A013_Problemas_de_Classificacao_Algoritmos

PoD Academy
Formação Full Stack Data & Analytics

Aula A013 - Problemas de Classificação
Modelos Supervisionados

Roberto Soares - LfLngLrnng

in/roberto-dos-santos-soares

Portifólio: roberto-ssoares

PoD Academy - Prof. Bruno Jardim
[+] Faturamento,
[-] Custo,
[+] Qualidade de vida

CRISP-DM (Modeling) | Desenvolvimento de Modelos

  • A Etapa de modelagem no processo CRISP-DM é fundamental para a construção de modelos preditivos, onde
    técnicas e algoritmos são selecionados e aplicados para criar modelos que aprendem ou inferem padrões a partir dos dados.
  • Nesta fase, os dados preparados na etapa anterior são usados para treinar modelos, ajustar parâmetros e
    validar os resultados, visando encontrar o modelo que melhor atende os objetivos do projeto.
  • A modelagem é um ponto crítico do CRISP-DM, exigindo um abordagem iterativa e o teste de diferentes hipóteses e
    estruturas de modelagem para otimizar a precisão e a robustez do modelo final.

image.png

Modelos Supervisionados - Problemas de Classificação

image.png

Instalando e Carregando Pacotes

  • Para atualizar um pacote, execute o comando abaixo no terminal ou prompt de comando:
    • pip install -U nome_pacote
  • Para instalar a versão exata de um pacote, execute o comando abaixo no terminal ou prompt de comando:
    • !pip install nome_pacote==versão_desejada
  • Depois de instalar ou atualizar o pacote, reinicie o jupyter notebook.
  • Instala o pacote watermark
    • Esse pacote é usado para gravar as versões de outros pacotes usados neste jupyter notebook.
#!pip install -q -U watermark
#!pip install -q -U lightgbm
#!pip install -q -U xgboost
#!pip install -q -U catboost
#!pip install -q -U scipy
#!conda install -q anaconda::scipy

Importando Bibliotecas:

import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import itertools
import lightgbm as lgb
import xgboost as xgb
import pickle

from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, roc_curve, precision_recall_curve, roc_auc_score, auc 
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from catboost import CatBoostClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.svm import SVC
from sklearn.ensemble import StackingClassifier
from sklearn.preprocessing import label_binarize
#from scipy import interp
from itertools import cycle

interp = np.interp

import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
matplotlib.rcParams['font.size'] = 10
matplotlib.rcParams['figure.figsize'] = (12, 6)
#matplotlib.rcParams['figure.facecolor'] = '#00000000'
#pd.options.display.max_columns = 20
#pd.options.display.max_rows = 20
#pd.options.display.max_colwidth = 80
np.set_printoptions(precision=4, suppress=True)
# Versões dos pacotes usados neste jupyter notebook
%reload_ext watermark
%watermark -a "Roberto Soares - LfLngLrnng"
Author: Roberto Soares - LfLngLrnng

Funções que utilizaremos neste projeto

Função de Cálculo da Estatística do KS

  • O procedimento Teste de Kolmogorov-Smirnov de Uma Amostra compara a função de distribuição cumulativa observada de uma variável com uma distribuição teórica especificada,
    que pode ser normal, uniforme, Poisson ou exponencial.
  • O Z de Kolmogorov-Smirnov é calculado a partir da diferença maior (em valor absoluto) entre as funções de distribuição cumulativa observadas e teóricas.
  • Esse teste de Qualidade do ajuste testa se as observações podem razoavelmente ter vindo da distribuição especificada.

calcular_ks_statistic()

  • A função calcular_ks_statistic calcula a estatística KS (Kolmogorov-Smirnov) para avaliar o desempenho de um modelo de classificação.
  • A estatística KS mede a distância máxima entre as distribuições cumulativas de duas amostras (eventos e não eventos), ajudando a verificar o poder discriminatório do modelo.
  • Vamos detalhar a lógica da função passo a passo:
  1. Criação do DataFrame
    • Um DataFrame "df" é criado a partir das listas ou arryays "y_score" (pontuações preditas pelo modelo) e "y_true" (valores verdadeiros das classes).
  2. Ordenação do DataFrame
    • O DataFrame é ordenado em ordem decrescente com base na pontuação ("score"). Isso garante que as maiores pontuações (mais prováveis de serem positivas) estejm no topo.
  3. Cálculo do número total de events non_events
    • "total_events" é a soma da coluna "target", representando o número total de eventos (casos positivos).
    • "total_non_events" é o total de observações menos o número de eventos, representando o número total de não eventos(casos negativos).
  4. Cálculo das somas acumuladas de events == 0).cumsum()
    • "cum_events" é a soma acumulada dos eventos à medida que percorremos o DataFrame.
    • "cum_non_events" é a soma acumulada dos não eentos (onde "target" é 0).
  5. Cálculo das porcentagens acumulada events / total_non_events
    • "cum_events_percent" é a porcentagem acumulada de eventos até cada ponto do DataFrame.
    • "cum_non_events_percent" é a porcentagem acumulada de não eventos até cada ponto do DataFrame.df.cum_non_events_percent).max()
    • A estatística KS é a diferença máxima entre as porcentagens acumuladas de events e non_events ao longo de todas as pontuações.

return ks_statistic

  • A função retorna o valor da estatística KS, que representa a maior divergência entre as distribuições cumulativas de eventos e não eventos.
  • Este valor é utilizado para avaliar o desempenho do modelo de classificação: quanto maior o valor, melhor é a capacidade do modelo de discriminar entre eventos e não eventos.

Em resumo, a função calcula a estatística KS comparando as distribuições cumulativas de eventos e não eventos, fornecendo uma medida do poder discriminatório do modelo de classificação.

image.png

import numpy as np
import pandas as pd
from sklearn.metrics import confusion_matrix, roc_curve, precision_recall_curve, roc_auc_score
import itertools
import matplotlib.pyplot as plt

def calcular_ks_statistic(y_true, y_score):
    df = pd.DataFrame({'score': y_score, 'target': y_true})
    df = df.sort_values(by='score', ascending=False)
    total_events = df.target.sum()
    total_non_events = len(df) - total_events
    df['cum_events'] = df.target.cumsum()
    df['cum_non_events'] = (df.target == 0).cumsum()
    df['cum_events_percent'] = df.cum_events / total_events
    df['cum_non_events_percent'] = df.cum_non_events / total_non_events
    ks_statistic = np.abs(df.cum_events_percent - df.cum_non_events_percent).max()
    return ks_statistic
def avaliar_modelo(X_train, y_train, X_test, y_test, modelo, nm_modelo):

    feature_names = list(X_train.columns)
    # Criação da figura e dos eixos
    fig, axs = plt.subplots(5, 2, figsize=(15, 30))  # Ajustado para incluir novos gráficos
    plt.tight_layout(pad=6.0)

    # Cor azul claro
    cor = 'skyblue'

    # Taxa de Evento e Não Evento
    event_rate = np.mean(y_train)
    non_event_rate = 1 - event_rate
    axs[0, 0].bar(['Evento', 'Não Evento'], [event_rate, non_event_rate], color=[cor, 'lightcoral'])
    axs[0, 0].set_title('Taxa de Evento e Não Evento')
    axs[0, 0].set_ylabel('Proporção')

    # Importância dos Atributos
    importancias = None
    if hasattr(modelo, 'coef_'):
        importancias = np.abs(modelo.coef_[0])
    elif hasattr(modelo, 'feature_importances_'):
        importancias = modelo.feature_importances_

    if importancias is not None:
        importancias_df = pd.DataFrame({'feature': feature_names, 'importance': importancias})
        importancias_df = importancias_df.sort_values(by='importance', ascending=True)

        axs[0, 1].barh(importancias_df['feature'], importancias_df['importance'], color=cor)
        axs[0, 1].set_title('Importância das Variáveis - ' + nm_modelo)
        axs[0, 1].set_xlabel('Importância')

    else:
        axs[0, 1].axis('off')  # Desativa o subplot se não houver importâncias para mostrar

    # Confusion Matrix - Treino
    y_pred_train = modelo.predict(X_train)
    cm_train = confusion_matrix(y_train, y_pred_train)
    axs[1, 0].imshow(cm_train, interpolation='nearest', cmap=plt.cm.Blues)
    axs[1, 0].set_title('Confusion Matrix - Treino - ' + nm_modelo)
    axs[1, 0].set_xticks([0, 1])
    axs[1, 0].set_yticks([0, 1])
    axs[1, 0].set_xticklabels(['0', '1'])
    axs[1, 0].set_yticklabels(['0', '1'])
    thresh = cm_train.max() / 2.
    for i, j in itertools.product(range(cm_train.shape[0]), range(cm_train.shape[1])):
        axs[1, 0].text(j, i, format(cm_train[i, j], 'd'),
                 horizontalalignment="center",
                 color="white" if cm_train[i, j] > thresh else "black")

    # Confusion Matrix - Teste
    y_pred_test = modelo.predict(X_test)
    cm_test = confusion_matrix(y_test, y_pred_test)
    axs[1, 1].imshow(cm_test, interpolation='nearest', cmap=plt.cm.Blues)
    axs[1, 1].set_title('Confusion Matrix - Teste - ' + nm_modelo)
    axs[1, 1].set_xticks([0, 1])
    axs[1, 1].set_yticks([0, 1])
    axs[1, 1].set_xticklabels(['0', '1'])
    axs[1, 1].set_yticklabels(['0', '1'])
    thresh = cm_test.max() / 2.
    for i, j in itertools.product(range(cm_test.shape[0]), range(cm_test.shape[1])):
        axs[1, 1].text(j, i, format(cm_test[i, j], 'd'),
                 horizontalalignment="center",
                 color="white" if cm_test[i, j] > thresh else "black")

    # ROC Curve - Treino e Teste
    y_score_train = modelo.predict_proba(X_train)[:, 1]
    fpr_train, tpr_train, _ = roc_curve(y_train, y_score_train)
    axs[2, 0].plot(fpr_train, tpr_train, color=cor, label='Treino')

    y_score_test = modelo.predict_proba(X_test)[:, 1]
    fpr_test, tpr_test, _ = roc_curve(y_test, y_score_test)
    axs[2, 0].plot(fpr_test, tpr_test, color='darkorange', label='Teste')

    axs[2, 0].plot([0, 1], [0, 1], color='navy', linestyle='--')
    axs[2, 0].set_title('ROC Curve - Treino e Teste - ' + nm_modelo)
    axs[2, 0].set_xlabel('False Positive Rate')
    axs[2, 0].set_ylabel('True Positive Rate')
    axs[2, 0].legend(loc="lower right")

    # Precision-Recall Curve - Treino e Teste
    precision_train, recall_train, _ = precision_recall_curve(y_train, y_score_train)
    axs[2, 1].plot(recall_train, precision_train, color=cor, label='Treino')

    precision_test, recall_test, _ = precision_recall_curve(y_test, y_score_test)
    axs[2, 1].plot(recall_test, precision_test, color='darkorange', label='Teste')

    axs[2, 1].set_title('Precision-Recall Curve - Treino e Teste - ' + nm_modelo)
    axs[2, 1].set_xlabel('Recall')
    axs[2, 1].set_ylabel('Precision')
    axs[2, 1].legend(loc="upper right")

    # Gini - Treino e Teste
    auc_train = roc_auc_score(y_train, y_score_train)
    gini_train = 2 * auc_train - 1
    auc_test = roc_auc_score(y_test, y_score_test)
    gini_test = 2 * auc_test - 1
    axs[3, 0].bar(['Treino', 'Teste'], [gini_train, gini_test], color=[cor, 'darkorange'])
    axs[3, 0].set_title('Gini - ' + nm_modelo)
    axs[3, 0].set_ylim(0, 1)
    axs[3, 0].text('Treino', gini_train + 0.01, f'{gini_train:.2f}', ha='center', va='bottom')
    axs[3, 0].text('Teste', gini_test + 0.01, f'{gini_test:.2f}', ha='center', va='bottom')

    # KS - Treino e Teste
    ks_train = calcular_ks_statistic(y_train, y_score_train)
    ks_test = calcular_ks_statistic(y_test, y_score_test)
    axs[3, 1].bar(['Treino', 'Teste'], [ks_train, ks_test], color=[cor, 'darkorange'])
    axs[3, 1].set_title('KS - ' + nm_modelo)
    axs[3, 1].set_ylim(0, 1)
    axs[3, 1].text('Treino', ks_train + 0.01, f'{ks_train:.2f}', ha='center', va='bottom')
    axs[3, 1].text('Teste', ks_test + 0.01, f'{ks_test:.2f}', ha='center', va='bottom')


    # Decile Analysis - Teste
    scores = modelo.predict_proba(X_test)[:, 1]
    noise = np.random.uniform(0, 0.0001, size=scores.shape)  # Adiciona um pequeno ruído
    scores += noise
    deciles = pd.qcut(scores, q=10, duplicates='drop')
    decile_analysis = y_test.groupby(deciles, observed=False).mean()
    axs[4, 1].bar(range(1, len(decile_analysis) + 1), decile_analysis, color='darkorange')
    axs[4, 1].set_title('Ordenação do Score - Teste - ' + nm_modelo)
    axs[4, 1].set_xlabel('Faixas de Score')
    axs[4, 1].set_ylabel('Taxa de Evento')

    # Decile Analysis - Treino
    scores_train = modelo.predict_proba(X_train)[:, 1]
    noise = np.random.uniform(0, 0.0001, size=scores_train.shape)  # Adiciona um pequeno ruído
    scores_train += noise
    deciles_train = pd.qcut(scores_train, q=10, duplicates='drop')
    decile_analysis_train = y_train.groupby(deciles_train, observed=False).mean()
    axs[4, 0].bar(range(1, len(decile_analysis_train) + 1), decile_analysis_train, color=cor)
    axs[4, 0].set_title('Ordenação do Score - Treino - ' + nm_modelo)
    axs[4, 0].set_xlabel('Faixas de Score')
    axs[4, 0].set_ylabel('Taxa de Evento')

    # Mostrar os gráficos
    plt.show()

Vamos ler a tabela de dados pós aplicação da preparação de dados:

  • Lembrar sempre de que para todas as etapas de tratamento de dados é importante separar uma porção dos dados para teste.
  • Neste notebook o nosso foco é explicar como os algorítmos de ML funcionam e por este motivo não vamos focar no dataprep (aula que já foi dada) e sim nos algoritmos de ML.
caminho = 'D:/_jupyter/pod/ds/mod06-ciencia-de-dados/data/'
abt_titanic_csv = 'abt_titanic.csv'

import pandas as pd

abt_00 = pd.read_csv(caminho+abt_titanic_csv,index_col=0)
abt_00.head()
Pclass Age SibSp Parch Ticket Fare Embarked Titulo Sex_male Survived
0 1.0 0.271174 0.125 0.0 523 0.014151 3 3 1.0 0
1 0.0 0.472229 0.125 0.0 596 0.139136 0 4 0.0 1
2 1.0 0.321438 0.000 0.0 669 0.015469 3 2 0.0 1
3 0.0 0.434531 0.125 0.0 49 0.103644 3 4 0.0 1
4 1.0 0.434531 0.000 0.0 472 0.015713 3 3 1.0 0

Vamos separar os dados em duas partes:

  • Variáveis explicativas
  • Target
# Separando as variáveis de entrada (features) e de saída (target)
X_titanic = abt_00.drop(columns=["Survived"])
y_titanic = abt_00["Survived"]

Validação Cruzada - Holdout

  • No contexto de Machine Learning, o método de "holdout" é uma técnica de validadção de modelos em que o conjunto de dados original é dividido em doi subconjuntos:
    • um para treinamento
    • e outro para teste.
  • Geralmente, uma proporção comum para essa divisão é 70% dos dados para treinamento e 30% para teste, embora essa proporção possa variar.
  • O conjunto de treinamento é utilizado para construir o modelo, enquanto o conjunto de teste é reservado para avaliar o desempenho do modelo em dados não vistos previamente,
    permitindo um estimativa mais realista de sua precisão, robustez e capacidade de genealização.
  • O método de holdout é simples, em eficaz, sendo essencial para evitar o Overfitting e garantir que modelo possa fazer previsões precisas em dados desconhecidos.

image.png

from sklearn.model_selection import train_test_split

  • Separando uma amostra de 70% para treinar o modelo e 30% para testar o modelo
  • Holdout 70/30 (out-of-sample)
# Dividindo os dados em conjunto de treinamento e teste (70% treino, 30% teste)
X_train, X_test, y_train, y_test = train_test_split(X_titanic,
                                                    y_titanic,
                                                    test_size=0.3,
                                                    random_state=42)
X_train.shape,X_test.shape
((623, 9), (268, 9))

1. Algoritmo - [Árvore de Decisão]

  • Árvores de Decisão são algoritmos de aprendizado supervisionado utilizados tanto para classificação quanto para regressão.
  • Elas funcionam dividindo um conjunto de dados em subconjuntos menores e mais homogêneos, com base nas variáveis independentes (features).
  • O objetivo é criar uma estrutura hierárquica que se parece com uma árvore, onde cada nó interno representa uma condição de decisão (baseada em uma feature), e cada
    folha representa um resultado ou classe final.

Como funciona:

  1. Raiz: A árvore começa com a variável (feature) mais importante, que é aquela que melhor divide os dados. Essa variável é escolhida usando
    critérios como o Gini Impurity ou a Entropia (para problemas de classificação) e o Erro Quadrático Médio (para regressão).

  2. Divisões: O algoritmo continua dividindo os dados em subconjuntos, criando novos nós, até que certas condições sejam atendidas, como:

    • O número mínimo de amostras por nó é alcançado.
    • A profundidade máxima da árvore é atingida.
    • Os dados em um nó se tornam suficientemente puros (homogêneos).
  3. Folhas: Os nós finais da árvore são chamados de folhas, e eles representam as previsões do modelo.

Vantagens:

  • Simples de entender e interpretar.
  • Não requer normalização ou padronização dos dados.
  • Pode lidar com dados categóricos e numéricos.
  • É possível visualizar o processo de tomada de decisão.

Desvantagens:

  • Pode se tornar complexa e propensa a overfitting em árvores muito profundas (solucionável com poda ou definindo a profundidade máxima).
  • Sensível a pequenas variações nos dados, o que pode resultar em uma árvore completamente diferente.

Hiperparâmetros da Árvore:

  • criterion='gini' --> Critério utilizado para medir a qualidade de uma divisão ('gini' ou 'entropy').
  • splitter='best' --> Estratégia utilizada para escolher a divisão em cada nó ('best' ou 'random').
  • max_depth=3 --> Profundidade máxima da árvore. None significa que os nós serão expandidos até que todas as folhas sejam puras ou contenham menos amostras do que min_samples_split.
  • min_samples_split=0.2 --> Número mínimo de amostras necessárias para dividir um nó interno.
  • min_samples_leaf=1 --> Número mínimo de amostras necessárias para estar em um nó folha.
  • min_weight_fraction_leaf=0.0 --> Fração mínima ponderada do total da soma dos pesos necessária para estar em um nó folha.
  • max_features=None --> Número de recursos a serem considerados ao procurar a melhor divisão.
  • random_state=None --> Semente do gerador de números aleatórios utilizado para a tomada de decisões de divisão quando splitter == 'random'.
  • max_leaf_nodes=None --> Número máximo de nós folha.
  • min_impurity_decrease=0.0 --> Um nó será dividido se a divisão induzir um decréscimo na impureza maior ou igual a este valor.
  • class_weight=None --> Peso das classes. Pode ser um dicionário de classes ou 'balanced'.

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definindo o modelo
model = DecisionTreeClassifier(random_state=42)

# Definindo os parâmetros para o grid search
param_grid = {
    'criterion':['gini','entropy'],
    'splitter':['best','random'],
    'max_depth': [2,3,4], # Profundidade máxima da árvore
    'min_samples_split': [0.008, 0.01,0.02,0.04], # Número mínimo de amostras necessárias para dividir um nó interno.
    'min_samples_leaf': [0.008,0.02,0.04], # Número mínimo de amostras necessárias para estar em um nó folha.
}

# Calculando a quantidade total de modelos que serão treinados
# 5 é o número de folds na validação cruzada (cv)
num_models = len(param_grid['splitter']) * len(param_grid['criterion']) * len(param_grid['max_depth']) * len(param_grid['min_samples_split']) * len(param_grid['min_samples_leaf']) * 5  
print(f"Total de Modelos a serem Treinados: {num_models}")

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)

# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_dt = grid.best_estimator_
Total de Modelos a serem Treinados: 720
Melhores Parâmetros:  {'criterion': 'entropy', 'max_depth': 4, 'min_samples_leaf': 0.008, 'min_samples_split': 0.008, 'splitter': 'random'}
Melhor AUC:  0.8515684369690272
CPU times: total: 438 ms
Wall time: 5 s

Avaliando o modelo (Árvore de Decisão)

avaliar_modelo(X_train, y_train, X_test, y_test, best_model_dt,nm_modelo='Árvore de Decisão')
Graph

2. Algoritmo - [Regressão Logística]

  • A Regressão Logística é um método estatístico utilizado para modelar a relação entre uma variável dependente binária e uma ou mais variáveis independentes.
  • Diferentemente da regressão linear, que prevê um valor contínuo, a regressão logística lida com a probabilidade de um evento ocorrer e é, portanto, adequada para problemas de classificação.

image.png

Na regressão logística, a transformação logit da probabilidade de um evento é modelada como uma combinação linear das variáveis independentes, ou seja.

$ \ln\left(\frac{p}{1-p}\right) = \beta_0 + \beta_1x_1 + \beta_2x_2 + \ldots + \beta_kx_k $

onde $ p $ é a probabilidade do evento de interesse, $ \beta_0, \beta_1, \ldots, \beta_k $ são os coeficientes a serem estimados, e $ x_1, x_2, \ldots, x_k $ são as variáveis independentes.
A probabilidade $ p $ é então obtida por meio da função logística inversa

$ p = \frac{1}{1 + e^{-(\beta_0 + \beta_1x_1 + \beta_2x_2 + \ldots + \beta_kx_k)}} $,

resultando em valores entre 0 e 1, que podem ser convertidos em classificações binárias, com base em um limiar, comumente 0,5.

Objetivo: Entender o conceito, a matemática e a aplicação da Regressão Logística em problemas de classificação binária.

Conteúdo:

  1. Introdução à Regressão Logística:

    • Definição: A regressão logística é um modelo estatístico usado para modelar a probabilidade de um evento binário ocorrer.
    • Aplicação: É amplamente utilizada para problemas de classificação binária em diversas áreas como medicina, finanças e marketing.
    • Exemplo: Pode ser usado para prever se um email é spam (1) ou não (0) baseado na frequência de certas palavras.
    • É muito utilizada na área de crédito porque possui explicabilidade e gera score com alta granularidade
  2. Modelo Matemático:

  • Função Logística (ou Sigmoide):

$ P(Y=1) = \frac{1}{1 + e^{-(\beta_0 + \beta_1X)}} $

  • onde $ P(Y=1) $ é a probabilidade do evento ocorrer, $ \beta_0 $ é o intercepto, $ \beta_1 $ é o coeficiente da variável $ X $, e $ e $ é a base do logaritmo natural.

  • Equação Logit:

$ \ln\left(\frac{P(Y=1)}{1-P(Y=1)}\right) = \beta_0 + \beta_1X $

  • onde $ \ln $ é o logaritmo natural.
  1. Estimativa de Parâmetros:
    • Os parâmetros $ \beta_0 $ e $ \beta_1 $ são estimados usando o método de Máxima Verossimilhança (Maximum Likelihood Estimation - MLE).
    • O objetivo do MLE é encontrar os valores dos parâmetros que maximizam a probabilidade de observar a amostra dada.
  1. Parâmetros $ \beta $ (Coeficientes) Os parâmetros $ \beta $ são os coeficientes do modelo, que são ajustados durante o treinamento do modelo para minimizar o erro entre as previsões do modelo
    e os dados reais. Eles multiplicam cada característica de entrada $ X $:

$ \ln\left(\frac{P(Y=1)}{1-P(Y=1)}\right) = \beta_0 + \beta_1X_1 + \beta_2X_2 + ... + \beta_kX_k $

  1. penalty (Penalidade) e C (Inverso da Regularização) A penalidade e o parâmetro $ C $ estão relacionados à regularização do modelo, que ajuda a evitar o sobreajuste.
    A penalidade é o tipo de regularização (L1, L2 ou Elasticnet), e $ C $ é o inverso da força de regularização.
  • L1 (Lasso):

$ J(\beta) = -\frac{1}{n}\sum_{i=1}^{n}\left[y_i\ln(P(Y_i=1)) + (1-y_i)\ln(1-P(Y_i=1))\right] + \lambda\sum_{j=1}^{k}|\beta_j| $

  • L2 (Ridge):

$ J(\beta) = -\frac{1}{n}\sum_{i=1}^{n}\left[y_i\ln(P(Y_i=1)) + (1-y_i)\ln(1-P(Y_i=1))\right] + \lambda\sum_{j=1}^{k}\beta_j^2 $

  • Elasticnet (Combinação de L1 e L2):

$ J(\beta) = -\frac{1}{n}\sum_{i=1}^{n}\left[y_i\ln(P(Y_i=1)) + (1-y_i)\ln(1-P(Y_i=1))\right] + \lambda\left(\alpha\sum_{j=1}^{k}|\beta_j| + \frac{1-\alpha}{2}\sum_{j=1}^{k}\beta_j^2\right) $

Onde:

  • $ J(\beta) $ é a função de custo (perda logarítmica com termo de regularização),
  • $ \lambda $ é o parâmetro de regularização (equivalente a $ \frac{1}{C} $),
  • $ \alpha $ é o parâmetro de mistura para Elasticnet (l1_ratio no scikit-learn),
  • $ n $ é o número de amostras,
  • $ k $ é o número de variáveis.
  1. solver (Solver) O parâmetro solver determina o algoritmo de otimização usado para encontrar os parâmetros $ \beta $ que minimizam a função de custo $ J(\beta) $. Diferentes
    solvers têm diferentes propriedades matemáticas e computacionais, e a escolha do solver pode afetar a velocidade e a qualidade da solução encontrada.
  • liblinear

    • Algoritmo: Library for Large Linear Classification.
    • Uso: Bom para conjuntos de dados pequenos e problemas de alta dimensionalidade.
    • Penalidades Suportadas: L1 e L2.
    • Problemas Multi-classe: Resolve utilizando a estratégia one-vs-rest (OvR).
    • Nota: Este solver não suporta a configuração "dual=True" quando o número de amostras é maior que o número de features.
  • newton-cg

    • Algoritmo: Método de Newton com Conjugate Gradient.
    • Uso: Bom para conjuntos de dados de tamanho médio e grande.
    • Penalidades Suportadas: Apenas L2 e sem penalidade.
    • Problemas Multi-classe: Resolve utilizando a perda logística multinomial.
  • lbfgs

    • Algoritmo: Limited-memory Broyden–Fletcher–Goldfarb–Shanno Algorithm.
    • Uso: Bom para conjuntos de dados de tamanho médio e grande.
    • Penalidades Suportadas: Apenas L2 e sem penalidade.
    • Problemas Multi-classe: Resolve utilizando a perda logística multinomial.
    • Nota: Este é o solver padrão e é um bom ponto de partida, mas pode ter problemas de convergência para alguns conjuntos de dados.
  • sag

    • Algoritmo: Stochastic Average Gradient Descent.
    • Uso: Rápido para conjuntos de dados grandes.
    • Penalidades Suportadas: Apenas L2 e sem penalidade.
    • Problemas Multi-classe: Resolve utilizando a perda logística multinomial.
    • Nota: Este solver é robusto para problemas de larga escala, mas pode ter problemas de convergência se o número de amostras não for suficientemente grande.
  • saga

    • Algoritmo: Stochastic Average Gradient Descent com suporte para penalidade Elasticnet.
    • Uso: Rápido para conjuntos de dados grandes e suporta penalidade Elasticnet.
    • Penalidades Suportadas: L1, L2, Elasticnet, e sem penalidade.
    • Problemas Multi-classe: Resolve utilizando a perda logística multinomial.
    • Nota: Este é o solver mais versátil, mas exige um número adequado de amostras para convergência.

Escolha do Solver:

  • Para conjuntos de dados pequenos, "liblinear" é uma boa escolha.
  • Para conjuntos de dados de tamanho médio e grande, "newton-cg", "lbfgs", "sag" ou "saga" são boas escolhas.
  • Se a penalidade Elasticnet for necessária, "saga" é a única opção.
  • Para problemas multiclasse, "newton-cg", "lbfgs", "sag", ou "saga" são recomendados.

Ao ajustar o modelo, é útil experimentar diferentes solvers para ver qual oferece o melhor desempenho para o seu conjunto de dados específico.

  1. max_iter (Máximo de Iterações) Este parâmetro determina o número máximo de iterações que o solver de otimização deve realizar antes de terminar. Isso é importante para
    garantir que o solver não continue indefinidamente se não conseguir encontrar uma solução.

  2. multi_class (Multi Classe) Determina a estratégia para lidar com problemas de classificação multiclasse, pode ser ‘ovr’ (one-vs-rest) que treina um modelo
    por classe ou ‘multinomial’ que treina um único modelo.

Estes são alguns dos principais parâmetros matemáticos e suas relações na regressão logística. Cada um desses parâmetros permite ao usuário
ajustar o modelo de acordo com as necessidades específicas de seu problema e conjunto de dados.

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definindo o modelo
model = LogisticRegression(max_iter=100)
model_sgs = LogisticRegression(max_iter=100)

# penalty='l2', # Tipo de regularização: 'l1', 'l2', 'elasticnet', 'none'
# dual=False, # Formulação primal e dual do problema de otimização da regressão logística, sendo recomendado manter como False quando temos mais amostras do que Variáveis
# tol=0.0001, # Tolerância para critério de parada.
# C=1.0, # Inverso da força de regularização; deve ser um valor flutuante positivo. Como valores menores, especifica uma regularização mais forte.
# fit_intercept=True, # Especifica se uma constante (ou seja, viés ou interceptação) deve ser adicionada à função de decisão.
# intercept_scaling=1, # Útil apenas quando o solver 'liblinear' é usado e self.fit_intercept é definido como True.
# class_weight=None, # Pesos associados às classes. Se não for fornecido, todas as classes são supostas ter peso um.
# random_state=None, # Semente usada pelo gerador de números aleatórios.
# solver='lbfgs', # Algoritmo a ser usado no problema de otimização: {'newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga'}, padrão='lbfgs'
# max_iter=100, # Número máximo de iterações para os solvers convergirem.
# multi_class='auto', # Se a opção escolhida for 'ovr', então um problema binário é ajustado para cada rótulo. Para 'multinomial', a perda minimizada é a perda multinomial ajustando todo o conjunto de dados de uma vez.
# verbose=0, # Para o solver 'liblinear' e 'lbfgs', defina o valor para qualquer inteiro positivo para classificar a verbosidade.
# warm_start=False, # Quando definido como True, reutiliza a solução da chamada anterior para ajustar como inicialização, caso contrário, apenas apaga a solução anterior.
# l1_ratio=None # O valor de Elastic-Net mixing parameter, com 0 <= l1_ratio <= 1. Somente usado se penalty='elasticnet'.

# Definindo os parâmetros para o grid search
param_grid = {
    'penalty':['l1','l2'],
    'tol':[0.00001,0.0001,0.001],
    'C': [1.0, 2.0],
    'solver':['liblinear', 'saga'],
    'class_weight': [None, 'balanced', {0: 1, 1: 2}, {0: 2, 1: 1}]
}

# Calculando a quantidade total de modelos que serão treinados
num_models = len(param_grid['penalty']) * len(param_grid['tol']) * len(param_grid['C']) * len(param_grid['solver']) * 5  # 5 é o número de folds na validação cruzada (cv)
print(f"Total de Modelos a serem Treinados: {num_models}")

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)

# treinando modelo sem grid search para comparra
model_sgs.fit(X_train, y_train)

# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_rl = grid.best_estimator_
Total de Modelos a serem Treinados: 120
Melhores Parâmetros:  {'C': 2.0, 'class_weight': {0: 1, 1: 2}, 'penalty': 'l1', 'solver': 'liblinear', 'tol': 0.001}
Melhor AUC:  0.8376319484915605
CPU times: total: 422 ms
Wall time: 2.17 s

Avaliando o modelo (Regressão Logística)

avaliar_modelo(X_train, y_train, X_test, y_test, model_sgs,nm_modelo='Reg Logística')
Graph
avaliar_modelo(X_train, y_train, X_test, y_test, best_model_rl,nm_modelo='Reg Logística')
Graph

Análise dos coeficientes da regressão logística

# coef_ retorna uma array 2D, então usamos [0] para obter a lista 1D de coeficientes.
coeficientes = best_model_rl.coef_[0]

# Criar um DataFrame com os nomes das variáveis e seus coeficientes correspondentes.
df_reglog = pd.DataFrame({'Variavel': list(X_train.columns),
                          'Coeficiente': coeficientes})

df_reglog['Importancia'] = df_reglog['Coeficiente'].abs().round(3)
df_reglog = df_reglog.sort_values(by='Importancia', ascending=False)

df_reglog
Variavel Coeficiente Importancia
8 Sex_male -2.576940 2.577
2 SibSp -2.288095 2.288
1 Age -1.831583 1.832
0 Pclass -1.576960 1.577
5 Fare 1.048738 1.049
3 Parch -0.423976 0.424
7 Titulo -0.266219 0.266
6 Embarked -0.198404 0.198
4 Ticket -0.000179 0.000

Bagging e Boosting

Bagging e Boosting são técnicas de ensemble em aprendizado de máquina que buscam melhorar a acurácia e robustez dos modelos.

Bagging, que é uma abreviação de "Bootstrap Agregation", envolve construir múltiplos modelos (tipicamente do mesmo tipo) a partir de diferentes subconjuntos de
dados de treinamento, escolhidos aleatóriamente com reposição
, e então agregar as previsões, geralmente através de votação majoritária para classificação ou média para regressão.

  • Random Forest é um exemplo clássico de um algoritmo que utiliza bagging.

Já o Boosting foca em combinar múltiplos modelos fracos sequencialmente, onde cada modelo tenta corrigir os erros do seu antecessor.

  • Isso é feito dando mais peso às observações que foram previstas incorretamente pelos modelos anteriores, forçando o novo modelo a focar mais nessas observações difíceis de acertar.
  • Exemplos de algoritmos que utilizam boosting incluem:
    • AdaBoost, Gradient Boosting, XGBoost, LightGBM e CatBoost.

Enquanto o bagging busca reduzir a variância do modelo, o boosting foca em reduzir o viés e é geralmente mais suscetível a "Overffiting" em comparação ao bagging.

image.png

3. Algoritmo - [Random Forest]

  • O Random Forest é um método de aprendizado de máquina ensemble que constrói múltiplas árvores de decisão durante o treinamento e gera a
    classe que é o modo das classes (classificação) ou a média das previsões (regressão) das árvores individuais para realizar previsões.
  • Ele introduz a aleatoriedade no processo de construção da árvore ao selecionar aleatoriamente um subcojunto de variáveis em cada divisão, o que
    ajuda a aumentar a diversidade entre as árvores e robustez do modelo geral.
  • O Random Forest é notável por sua flexibilidade, sendo capaz de modelar relações complexas nos dados, e por sua robustez a capacidade de generalização,
    mitigando o risco de "Overffiting" através do processo de ensemble.

image.png

O Random Forest é um método de ensemble learning que combina múltiplos modelos de árvore de decisão para criar um modelo mais robusto e preciso.

É particularmente eficaz para tarefas de classificação e regressão.

Como Funciona?

  1. Bootstrap Aggregating (Bagging):

    • O Random Forest utiliza uma técnica chamada Bagging (Bootstrap Aggregating).
    • Ele cria subconjuntos do conjunto de dados de treinamento usando amostragem com reposição (bootstrap) e treina uma árvore de decisão em cada subconjunto.
    • Se tivermos um conjunto de treinamento com (N) amostras, cada árvore é treinada com um subconjunto de (N) amostras, selecionadas aleatoriamente com reposição.
  2. Árvores de Decisão:

    • Cada árvore é construída usando um subconjunto de dados e é capaz de fazer previsões.
    • A árvore é dividida em nós, começando pelo nó raiz, e em cada nó, é feita uma decisão baseada em um dos atributos (features) para separar os dados em subconjuntos.
  3. Random Subspace Method:

    • Além do bagging, o Random Forest usa o método do subespaço aleatório.
    • Em cada divisão da árvore, um subconjunto aleatório de atributos é selecionado, e a divisão é feita com base no melhor atributo desse subconjunto.
    • Isso ajuda a garantir que as árvores sejam descorrelacionadas e aumenta a diversidade no ensemble.
  4. Agregação:

    • Para tarefas de classificação, o Random Forest usa votação majoritária. Cada árvore "vota" em uma classe, e a classe com mais votos é escolhida.
    • Para tarefas de regressão, o Random Forest calcula a média das previsões de todas as árvores.

Equações:

  • Classificação:

$ Y_{\text{pred}} = \text{moda}(Y_{\text{tree}_1}, Y_{\text{tree}_2}, ..., Y_{\text{tree}_n}) $

  • Regressão:

$ Y_{\text{pred}} = \frac{1}{n} \sum_{i=1}^{n} Y_{\text{tree}_i} $

onde $ Y_{\text{tree}_i} $ é a previsão da i-ésima árvore.

Vantagens do Random Forest:

  1. Robustez e Precisão:

    • Ao combinar várias árvores de decisão, o Random Forest tende a ser mais robusto e preciso do que uma única árvore de decisão.
  2. Manipulação de Dados Ausentes:

    • Pode manipular dados ausentes e diferentes tipos de variáveis (categóricas e numéricas).
  3. Importância das Variáveis:

    • Fornece uma medida de importância de variáveis, permitindo uma seleção de variáveis eficiente.

Aplicações:

O Random Forest é aplicável em várias áreas, como finanças para detecção de fraude, medicina para diagnóstico de doenças, e ciência de dados para classificação e regressão.

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definindo o modelo
model = RandomForestClassifier()

# n_estimators=100,  # Número de árvores na floresta.
# criterion='gini',  # Função para medir a qualidade de uma divisão ("gini" ou "entropy").
# max_depth=None,  # Profundidade máxima das árvores. None significa que os nós são expandidos até ficarem puros.
# min_samples_split=2,  # Número mínimo de amostras necessárias para dividir um nó interno.
# min_samples_leaf=1,  # Número mínimo de amostras necessárias para estar em um nó folha.
# min_weight_fraction_leaf=0.0,  # Fração mínima ponderada da soma total de pesos necessária para estar em um nó folha.
# max_features='auto',  # Número de características a considerar ao procurar a melhor divisão.
# max_leaf_nodes=None,  # Número máximo de nós folha.
# min_impurity_decrease=0.0,  # Um nó será dividido se a divisão induzir a uma diminuição da impureza maior ou igual a este valor.
# bootstrap=True,  # Se as amostras de bootstrap são usadas ao construir árvores.
# oob_score=False,  # Se usar amostras out-of-bag para estimar a precisão generalizada.
# n_jobs=None,  # Número de jobs a serem executados em paralelo para ajuste e previsão. -1 significa usar todos os processadores.
# random_state=None,  # Controla a aleatoriedade do bootstrap e da seleção de features.
# verbose=0,  # Controla a verbosidade do processo de treinamento.
# warm_start=False,  # Reutiliza a solução da chamada anterior para ajustar e adicionar mais estimadores ao ensemble.
# class_weight=None  # Pesos associados às classes. Pode ser "balanced", "balanced_subsample" ou um dicionário com os pesos.


# Definindo os parâmetros para o grid search
param_grid = {
    'n_estimators': [10, 50],
    'max_depth': [2, 3, 5],
    'min_samples_split': [0.001, 0.01],
    'min_samples_leaf': [2, 0.01],
    'bootstrap': [True, False],
    # 'max_features': ['auto', 'sqrt', 'log2']
}

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    verbose=2,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)

# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_rf = grid.best_estimator_
Fitting 5 folds for each of 48 candidates, totalling 240 fits
Melhores Parâmetros:  {'bootstrap': True, 'max_depth': 5, 'min_samples_leaf': 0.01, 'min_samples_split': 0.01, 'n_estimators': 50}
Melhor AUC:  0.8649436761554586
CPU times: total: 406 ms
Wall time: 3.88 s

Avaliando o modelo (Random Forest)

avaliar_modelo(X_train, y_train, X_test, y_test, best_model_rf,nm_modelo='Random Forest')
Graph

4. Algoritmo - [Gradient Boosting]

  • O Gradient Boosting é uma técnica de aprendizado de máquina para problemas de regressão e classificação, que constrói um modelo preditivo na forma de um conjunto
    de modelos de previsão fracos, (tipicamente árvores de decisão)
    , de forma iterativa, onde cada modelo tenta corrigir os erros residuais dos modelos anteriores.
  • Em cada iteração, o algoritmo calcula o gradiente negativo da função de perda, considerando o modelo já existente, e ajusta um novo modelo para prever esses resíduos.
  • As previsões desses modelos são então combinadas para formar o modelo final.
  • O Gradiente Boosting é conhecido por sua eficiência, sendo capaz de produzir modelos altamente precisos e aplicáveis a diversos tipos de dados e domínios,
    mas requer cuidadosa calibração de parâmetros e regularização para evitar "Overffiting".

image.png

1. Modelo Aditivo: O modelo aditivo gerado pelo Gradient Boosting é definido como:

$ F(x) = \sum_{i=1}^{M} \beta_i h(x; a_i) $

onde:

  • $M$: Número de árvores ou, mais geralmente, de modelos fracos.
  • $ \beta_i $: Peso da i-ésima árvore.
  • $ h(x; a_i) $: i-ésima árvore de decisão com parâmetros $ a_i $.

2. Função Objetivo e Otimização: O objetivo do Gradient Boosting é encontrar uma função $ F(x) $ que minimize a seguinte função objetivo:

$ L(F) = \sum_{i=1}^{N} l(y_i, F(x_i)) + \sum_{i=1}^{M} \Omega(h_i) $

onde:

  • $ l(y_i, F(x_i)) $ é a função de perda que compara a previsão do modelo, $ F(x_i) $, com o valor verdadeiro, $ y_i $.
  • $ \Omega(h_i) $ é um termo de regularização para a i-ésima árvore.

3. Atualização Sequencial: O Gradient Boosting constrói as árvores de forma sequencial. A cada passo, uma nova árvore é adicionada ao modelo para minimizar a função de perda, dado
o conjunto atual de árvores. Seja $ F_m(x) $ o modelo no passo $ m $, a árvore adicional é encontrada da seguinte maneira:

$ h_m = \arg\min_h \sum_{i=1}^{N} l(y_i, F_{m-1}(x_i) + h(x_i)) + \Omega(h) $

e o modelo é atualizado como:

$ F_m(x) = F_{m-1}(x) + \beta_m h_m(x) $

onde $ \beta_m $ é a taxa de aprendizado.

4. Gradiente Negativo:

Para encontrar a árvore que minimiza a função de perda, o Gradient Boosting utiliza o conceito de gradiente negativo. Em cada passo, é calculado o gradiente negativo da função
de perda em relação às previsões do modelo atual, e uma árvore é ajustada para prever esses resíduos. Matematicamente, o gradiente negativo para a i-ésima observação é:

$ r_{mi} = - \left[ \frac{\partial l(y_i, F(x_i))}{\partial F(x_i)} \right]_{F(x) = F_{m-1}(x)} $

5. Regularização:

O Gradient Boosting inclui termos de regularização para evitar o overfitting, que podem incluir a profundidade máxima das árvores, o número mínimo de observações em
um nó folha, e penalizações para o número total de folhas de uma árvore.

Conclusão:

O Gradient Boosting é uma técnica poderosa e flexível que pode oferecer um desempenho preditivo de alta qualidade em uma variedade de problemas de aprendizado de máquina,
sendo especialmente útil em contextos onde o desempenho preditivo é crítico.

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definindo o modelo
model = GradientBoostingClassifier(random_state=42)

# Definindo os parâmetros para o grid search
param_grid = {
    'learning_rate': [0.01, 0.1],
    'n_estimators': [100, 200],
    'max_depth': [3, 5, 7],
    'min_samples_split': [2, 4],
    'min_samples_leaf': [1, 2],
    'subsample': [0.8, 0.9],
    'max_features': ['sqrt', 'log2', None]
}

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    verbose=2,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)

# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_gbm = grid.best_estimator_
Fitting 5 folds for each of 288 candidates, totalling 1440 fits
Melhores Parâmetros:  {'learning_rate': 0.01, 'max_depth': 7, 'max_features': 'sqrt', 'min_samples_leaf': 1, 'min_samples_split': 4, 'n_estimators': 100, 'subsample': 0.8}
Melhor AUC:  0.8714340466966377
CPU times: total: 3 s
Wall time: 2min 57s

Avaliando o modelo (Gradient Boosting)

avaliar_modelo(X_train, y_train, X_test, y_test, best_model_gbm,nm_modelo='Gradient Boosting')
Graph

5. Algoritmo - [Light Gradiente Boost Machine (LightGBM)]

https://lightgbm.readthedocs.io/en/stable/

  • O LigthGBM, que significa Light Gradient Boosting Machine, é uma framework de apredizado de máquina desenvolvida pela Michosoft, otimizada para eficiência, escalabilidade e distribuição.
  • Diferente de outras implementações de Gradient Boosting que constroem árvores de decisão nível a nível (horizontalmente), o LightGBM cresce
    árvores folha a folha (verticalmente), escolhendo a folha que minimiza a perda para crescer a cada iteração.
  • Esta abordagem torna o LightGBM particularmente eficaz para conjuntos de dados grandes ou com uma grande quantidade de variáveis.
  • Além disso, o LightGBM suporta variáveis categóricas e é capaz de lidar com dados esparsos, e é adaptável a diferentes tipos de tarefas de aprendizado de máquina,
    como classificação, regressão e ranking, mantendo uma alta eficiência e precisão, tornando-o uma escolha popular em diversas aplicações práticas de aprendizado de máquina.

image.png

Vamos entender um pouco mais sobre como ele funciona, suas vantagens, e seus parâmetros.

Trata-se de uma implementação eficiente e escalável do Gradient Boosting, que permite construir modelos de alta precisão, sendo capaz de lidar com conjuntos de dados de grande escala.

Seu conjunto extenso de parâmetros permite um ajuste fino do modelo de acordo com as necessidades específicas de diferentes problemas de aprendizado de máquina.

Como Funciona o LightGBM:

  • O LightGBM, como um algoritmo de boosting, constrói modelos de forma sequencial, onde cada modelo tenta corrigir os erros do seu antecessor.
  • Diferentemente de outros algoritmos de boosting de árvore, o LightGBM cresce a árvore verticalmente (Leaf-wise) em vez de horizontalmente (Level-wise).
  • Isso significa que o LightGBM foca em dividir as folhas que trazem mais ganho de informação, em vez de dividir todas as folhas no mesmo nível.

Vantagens do LightGBM:

  1. Eficiência de Memória e Computacional: Pode lidar com conjuntos de dados grandes, sendo mais eficiente em termos de memória e computação.
  2. Alta Precisão: Oferece maior precisão em comparação com outros algoritmos de boosting.
  3. Suporte para Dados Categóricos: Pode lidar com dados categóricos sem necessidade de codificação one-hot.
  4. Paralelismo e GPU: Suporta treinamento paralelo e pode ser acelerado por GPU.

O LightGBM, sendo um método de boosting baseado em árvores, envolve muitos conceitos e cálculos.

Abaixo, vou resumir algumas das principais equações e conceitos envolvidos no treinamento de um modelo LightGBM.

  1. Modelo Aditivo: O modelo final do LightGBM é uma soma de árvores de decisão. Para um problema de regressão, a previsão $ \hat{y} $ é dada pela soma das previsões de todas as árvores $ T $:

$ \hat{y_i} = \sum_{k=1}^{T} f_k(x_i) $

Onde:

  • $ \hat{y_i} $ é a previsão para a i-ésima amostra.
  • $ T $ é o número total de árvores.
  • $ f_k(x_i) $ é a previsão da k-ésima árvore para a i-ésima amostra.
  1. Função Objetivo: O LightGBM otimiza uma função objetivo que é a soma de uma função de perda $ L $ e um termo de regularização $ \Omega $:

$ \text{Obj} = \sum_{i=1}^{N} L(y_i, \hat{y_i}) + \sum_{k=1}^{T} \Omega(f_k) $

Onde:

  • $ L(y_i, \hat{y_i}) $ é a função de perda, que mede a diferença entre a previsão $ \hat{y_i} $ e o valor verdadeiro $ y_i $.
  • $ \Omega(f_k) $ é o termo de regularização para a k-ésima árvore, que controla a complexidade do modelo.
  1. Função de Perda: A função de perda depende do tipo de problema. Por exemplo, para regressão, uma escolha comum é o erro quadrático médio:

$ L(y_i, \hat{y_i}) = \frac{1}{2} (y_i - \hat{y_i})^2 $

Para classificação binária, uma escolha comum é a perda logística:

$ L(y_i, \hat{y_i}) = -y_i \log(\hat{y_i}) - (1 - y_i) \log(1 - \hat{y_i}) $

  1. Regularização: O termo de regularização $ \Omega(f_k) $ geralmente inclui a soma da profundidade da árvore (número total de nós folha) e a norma dos scores nas folhas, e é formulado como:

$ \Omega(f_k) = \gamma T + \frac{1}{2} \lambda \sum_{j=1}^{T} w_j^2 $

Onde:

  • $ T $ é o número de folhas da árvore.
  • $ w_j $ é o score da j-ésima folha.
  • $ \gamma $ e $ \lambda $ são parâmetros de regularização.
  1. Gradiente e Hessiano:
  • O LightGBM utiliza o gradiente e o hessiano da função de perda para encontrar a direção ótima para otimizar as árvores subsequentes.
  • Estes são calculados com base na derivada primeira e segunda da função de perda, respectivamente.

$ g_i = \frac{\partial L(y_i, \hat{y_i})}{\partial \hat{y_i}}, \quad h_i = \frac{\partial^2 L(y_i, \hat{y_i})}{\partial \hat{y_i}^2} $

Essas equações e conceitos são fundamentais para entender o funcionamento interno do LightGBM. Cada passo do algoritmo envolve a otimização desses componentes para
construir um modelo que minimiza a função de perda enquanto mantém a complexidade do modelo sob controle.

Para problemas de classificação, o LightGBM, assim como outros métodos de boosting baseados em árvores, utiliza o conceito de votação de árvores para determinar a classe
final predita. Vou explicar como isso é feito, focando em classificação binária e multiclasse.

Classificação Binária: Para classificação binária, as árvores no modelo de boosting são usadas para modelar o log-odds (logit) da probabilidade da classe positiva. A combinação das árvores
dá o valor final do logit, que é então transformado em uma probabilidade via função logística, e a classificação final é decidida com base nessa probabilidade.

A fórmula para o logit (log-odds) é:

$ \text{logit}(\hat{y_i}) = \sum_{k=1}^{T} f_k(x_i) $

Onde $ f_k(x_i) $ é a previsão da k-ésima árvore para a i-ésima amostra.

Para converter o logit para uma probabilidade, usamos a função logística (sigmoid):

$ \hat{p_i} = \frac{1}{1 + e^{-\text{logit}(\hat{y_i})}} $

Finalmente, a classe predita $ \hat{y_i} $ é determinada com base na probabilidade:

$ \hat{y_i} = \begin{cases} 1 & \text{se } \hat{p_i} \geq 0.5 \\ 0 & \text{se } \hat{p_i} < 0.5 \end{cases} $

Classificação Multiclasse: Para problemas de classificação com mais de duas classes, o LightGBM utiliza o método de One-vs-All (OvA) para construir um modelo para cada classe contra
todas as outras. Para cada classe $ j $, é construído um modelo para estimar a probabilidade $ P(y = j | x) $.

A probabilidade para a classe $ j $ é calculada usando a função softmax:

$ \hat{p_{ij}} = \frac{e^{\text{score}_{ij}}}{\sum_{k=1}^{C} e^{\text{score}_{ik}}} $

Onde:

  • $ \text{score}_{ij} $ é o score para a classe $ j $ para a i-ésima amostra, calculado como a soma das previsões das árvores para essa classe.
  • $ C $ é o número total de classes.

A classe final predita $ \hat{y_i} $ é então a classe com a maior probabilidade:

$ \hat{y_i} = \arg\max_j \hat{p_{ij}} $

As árvores no modelo LightGBM, em conjunto, votam para calcular a probabilidade de uma amostra pertencer a uma determinada classe, e a classe final
é escolhida com base nessas probabilidades, seja através de um limiar, no caso de classificação binária, ou escolhendo a classe com a maior probabilidade, no caso de classificação multiclasse.

Curiosidade sobre a matemática do Gradient Boosting vs LightGBM

O uso do Hessiano, ou da segunda derivada da função de perda, traz várias vantagens no contexto dos métodos de boosting, como o LightGBM, especialmente
em relação à eficiência e precisão da otimização.

1. Informação da Curvatura:

  • O Hessiano fornece informações sobre a curvatura da função de perda, permitindo ao modelo entender não apenas a direção de mudança (fornecida pelo Gradiente), mas também a rapidez com que essa mudança ocorre.
  • Isso pode ajudar o modelo a fazer atualizações mais informadas e precisas dos parâmetros.

2. Ajuste Adaptativo da Taxa de Aprendizado:

  • O Hessiano pode ser usado para adaptar a taxa de aprendizado durante o treinamento, permitindo que o modelo ajuste a taxa de aprendizado de acordo com a curvatura da função de perda.
  • Isso pode resultar em uma convergência mais rápida e estável, especialmente em áreas onde a função de perda tem uma curvatura alta.

3. Otimização de Segunda Ordem:

  • O uso do Hessiano permite a implementação de métodos de otimização de segunda ordem, como o método de Newton-Raphson, que podem ser mais eficientes e precisos em
    encontrar o mínimo da função de perda em comparação com métodos de primeira ordem que usam apenas o gradiente.

4. Corte de Folhas Baseado em Ganho:

  • No contexto do LightGBM, onde as árvores são construídas leaf-wise, o Hessiano é também utilizado para calcular o ganho de informação de um split potencial,
    ajudando a decidir se um split é benéfico ou não.
  • Isso pode resultar em árvores mais otimizadas e eficientes.

Exemplo Simples: Considere uma função de perda quadrática $ L(y, \hat{y}) = \frac{1}{2}(y - \hat{y})^2 $. O gradiente e o Hessiano são, respectivamente:

$ g_i = \frac{\partial L(y_i, \hat{y_i})}{\partial \hat{y_i}} = -(y_i - \hat{y_i}) $

$ h_i = \frac{\partial^2 L(y_i, \hat{y_i})}{\partial \hat{y_i}^2} = 1 $

Neste caso, o Hessiano é constante, mas em funções de perda mais complexas ou não-convexas, ele varia e fornece informações valiosas sobre a forma da
função de perda, ajudando na otimização do modelo.

O Hessiano, ao prover informações adicionais sobre a curvatura da função de perda, permite uma otimização mais sofisticada e adaptativa, podendo resultar em modelos mais precisos
e eficientes em termos computacionais, especialmente em casos onde a função de perda é complexa e não-convexa.

Parâmetros Importantes do LightGBM:

Aqui estão alguns dos principais parâmetros do LightGBM e uma breve descrição de cada um:

  1. 'num_leaves' (int):
    • Número máximo de folhas em uma árvore.
    • Valor padrão: 31
  2. 'max_depth' (int):
    • Profundidade máxima da árvore.
    • Valor padrão: -1 (sem limite)
  3. 'learning_rate' (float):
    • Taxa de aprendizado.
    • Valor padrão: 0.1
  4. 'n_estimators' (int):
    • Número de árvores a serem construídas.
    • Valor padrão: 100
  5. 'objective' (string):
    • Função objetivo a ser usada.
    • Exemplos: "regression", "binary", "multiclass"
  6. 'boosting_type' (string):
    • Tipo de algoritmo de boosting a ser usado.
    • Valor padrão: "gbdt" (Gradient Boosted Decision Trees)
  7. 'subsample' (float):
    • Fração de amostras usadas por árvore.
    • Valor padrão: 1.0
  8. 'colsample_bytree' (float):
    • Fração de características usadas por árvore.
    • Valor padrão: 1.0
  9. 'min_child_samples' (int):
    • Número mínimo de amostras necessárias em uma folha.
    • Valor padrão: 20
  10. 'importance_type' (string):
    • Tipo de importância das características a serem preenchidas.
    • Valor padrão: "split"

import lightgbm as lgb
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definindo o modelo
model = lgb.LGBMClassifier(verbose=-1)

# Definindo os parâmetros para o grid search
param_grid = {
    'max_depth' : [2,3],
    'num_leaves': [5, 31],
    'reg_alpha': [0.1, 0.5],
    'min_data_in_leaf': [2, 5],
    'lambda_l1': [0, 1, 1.5],
    'lambda_l2': [0, 1]
}

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    verbose=2,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)


# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_lightgbm = grid.best_estimator_
Fitting 5 folds for each of 96 candidates, totalling 480 fits
Melhores Parâmetros:  {'lambda_l1': 0, 'lambda_l2': 1, 'max_depth': 2, 'min_data_in_leaf': 5, 'num_leaves': 5, 'reg_alpha': 0.1}
Melhor AUC:  0.8651497450429522
CPU times: total: 297 ms
Wall time: 8.08 s

Avaliando o modelo (LightGBM)

avaliar_modelo(X_train, y_train, X_test, y_test, best_model_lightgbm,nm_modelo='LightGBM')
Graph

eXtreme Gradient Boosting (XGBoost)

O XGBoost (eXtreme Gradient Boosting) é uma implementação eficiente e escalável do algoritmo de Gradient Boosting, projetada para ser mais rápida e melhor performática.

Ele é amplamente utilizado em competições de ciência de dados devido à sua flexibilidade e alta capacidade preditiva.

image.png

1. Modelo Aditivo e Função Objetivo:

Assim como outros métodos de boosting, o XGBoost constrói um modelo aditivo de forma sequencial, com o objetivo de minimizar uma função objetivo que consiste
em uma função de perda e um termo de regularização:

$ \text{Obj} = \sum_{i=1}^{N} L(y_i, \hat{y_i}) + \sum_{k=1}^{T} \Omega(f_k) $

  • $ L(y_i, \hat{y_i}) $ é a função de perda, que mede a diferença entre a previsão $ \hat{y_i} $ e o valor verdadeiro $ y_i $.
  • $ \Omega(f_k) $ é o termo de regularização para a k-ésima árvore.
  • $ T $ é o número total de árvores.

2. Regularização:

O termo de regularização no XGBoost é um dos elementos que o diferenciam de outras implementações de boosting. Ele é formulado como:

$ \Omega(f) = \gamma T + \frac{1}{2} \lambda \sum_{j=1}^{T} w_j^2 $

  • $ T $ é o número de folhas na árvore.
  • $ w_j $ é o score (valor previsto) da j-ésima folha.
  • $ \gamma $ e $ \lambda $ são parâmetros de regularização.

3. Otimização e Método de Newton-Raphson:

  • O XGBoost utiliza tanto o gradiente quanto o Hessiano da função de perda para otimizar o modelo, empregando o método de Newton-Raphson para encontrar o ótimo.
  • Para cada árvore, os parâmetros são ajustados da seguinte forma:

$ w_j^* = -\frac{\sum_{i \in I_j} g_i}{\sum_{i \in I_j} h_i + \lambda} $

  • $ g_i $ e $ h_i $ são, respectivamente, o gradiente e o Hessiano da função de perda para a i-ésima observação.
  • $ I_j $ é o conjunto de observações que caem na j-ésima folha.

4. Pruning:

  • O XGBoost constrói as árvores de forma "max_depth" primeiro (até a profundidade máxima especificada) e, em seguida, realiza o pruning (poda) das árvores com base no
    ganho de informação, removendo splits que não trazem melhoria suficiente.

5. Equações de Classificação e Regressão:

  • Para problemas de regressão, o XGBoost tenta prever o valor esperado da variável dependente.
  • Para problemas de classificação binária, ele modela a log-odds da probabilidade da classe positiva, e para classificação multiclasse, ele utiliza
    a técnica "softmax" para modelar as probabilidades das diferentes classes.

A função Softmax é uma função de ativação que transforma vetores de números reais em um vetor de valores de probabilidade. Ela é frequentemente usada em problemas
de classificação multiclasse para transformar os scores brutos (logits) de um modelo em probabilidades para cada classe.

Na saída de modelos de aprendizado de máquina para classificação multiclasse, como redes neurais ou modelos de regressão logística multiclasse, a função Softmax é frequentemente
usada para converter os scores brutos em probabilidades. A classe prevista para uma dada amostra é então simplesmente a classe com a maior probabilidade segundo a função Softmax.

Formulação da Função Softmax:

Dado um vetor $ z = [z_1, z_2, ..., z_K] $ de scores brutos (logits) para cada uma das $ K $ classes, a função Softmax transforma esses scores em probabilidades através da seguinte fórmula:

$ \text{Softmax}(z)_j = \frac{e^{z_j}}{\sum_{k=1}^{K} e^{z_k}} $

Onde:

  • $ \text{Softmax}(z)_j $ é a probabilidade da amostra pertencer à classe $ j $.
  • $ z_j $ é o score bruto (logit) para a classe $ j $.
  • O denominador $ \sum_{k=1}^{K} e^{z_k} $ é a soma dos exponenciais dos scores brutos para todas as classes, assegurando que a soma das probabilidades de todas as classes seja 1.

Hiperparametros Importantes

Taxa de aprendizado, determina o passo no ajuste dos pesos.

  • Valores comuns são entre 0.01 e 0.3.
  • learning_rate=0.1,

Número de árvores a serem construídas.

  • n_estimators=100,

Profundidade máxima de uma árvore. Valores maiores podem levar a overfitting.

  • max_depth=3,

Controle de overfitting. Valores maiores podem levar a underfitting.

  • É o peso mínimo necessário para criar um novo nó em uma árvore.
  • min_child_weight=1,

Fração de colunas (features) a serem amostradas para construir cada árvore.

  • colsample_bytree=1,

Fração de observações amostradas para construir cada árvore.

  • Valores baixos podem evitar overfitting.
  • subsample=1,

Parâmetro de regularização L1 nos pesos das folhas.

  • Um valor maior resulta em mais regularização.
  • reg_alpha=0,

Parâmetro de regularização L2 nos pesos das folhas.

  • reg_lambda=1,

Uma semente para reprodutibilidade.

  • random_state=42,

Métrica de avaliação a ser usada na validação.

  • Deve ser uma string referenciando uma métrica de avaliação apropriada.**
  • Exemplo: 'logloss' para problemas de classificação, 'rmse' para regressão.**
  • eval_metric='logloss',

Desativa o uso do label encoder para evitar warnings com versões mais recentes do XGBoost.

  • use_label_encoder=False

A log loss, ou logaritmic loss, também conhecida como cross-entropy loss, é uma métrica de erro frequentemente usada em problemas de classificação binária e multiclasse para avaliar a
precisão das previsões de probabilidade de um modelo.

1. Log Loss para Classificação Binária:

Para a classificação binária, onde temos duas classes, 0 e 1, a fórmula da log loss para uma única observação é:

$ L(y, p) = -\left( y \log(p) + (1 - y) \log(1 - p) \right) $

Onde:

  • $ y $ é o rótulo verdadeiro (0 ou 1).
  • $ p $ é a probabilidade prevista para a classe 1.

A log loss para um conjunto de $ N $ observações é a média das log losses para cada observação:

$ \text{Log Loss} = -\frac{1}{N} \sum_{i=1}^{N} \left( y_i \log(p_i) + (1 - y_i) \log(1 - p_i) \right) $

2. Log Loss para Classificação Multiclasse:

Para problemas de classificação multiclasse, a log loss se estende para lidar com múltiplas classes. Se temos $ C $ classes, a log loss para uma única observação é:

$ L(y, p) = - \sum_{c=1}^{C} y_c \log(p_c) $

Onde:

  • $ y_c $ é 1 se a classe verdadeira é $ c $ e 0 caso contrário.
  • $ p_c $ é a probabilidade prevista para a classe $ c $.

A log loss para um conjunto de $ N $ observações é a média das log losses para cada observação:

$ \text{Log Loss} = -\frac{1}{N} \sum_{i=1}^{N} \sum_{c=1}^{C} y_{ic} \log(p_{ic}) $

Propriedades da Log Loss:

  • A log loss avalia a qualidade das previsões de probabilidade, punindo fortemente as previsões que estão confiantemente incorretas.
  • O valor da log loss varia de 0 a $ +\infty $. Um modelo perfeito teria uma log loss de 0. Valores maiores indicam previsões piores.

import xgboost as xgb
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definir o modelo
model = xgb.XGBClassifier(use_label_encoder=False, eval_metric='mlogloss')

# Definindo os parâmetros para o grid search
param_grid = {
    'learning_rate': [0.01, 0.1],
    'max_depth': [3, 5],
    'min_child_weight': [1, 3],
    'subsample': [0.5, 0.7, 1.0],
    'colsample_bytree': [0.5, 0.7],
    'n_estimators': [10, 100]
}

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    verbose=2,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)


# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_xgbm = grid.best_estimator_
Fitting 5 folds for each of 96 candidates, totalling 480 fits
Melhores Parâmetros:  {'colsample_bytree': 0.7, 'learning_rate': 0.1, 'max_depth': 5, 'min_child_weight': 3, 'n_estimators': 100, 'subsample': 0.7}
Melhor AUC:  0.8769776773342389
CPU times: total: 703 ms
Wall time: 4.28 s

Avaliando o modelo (XGBoost)

avaliar_modelo(X_train, y_train, X_test, y_test, best_model_xgbm,nm_modelo='XGBoost')
Graph

CatBoost

  • https://catboost.ai/en/docs/concepts/installation

  • O CatBoost é uma biblioteca de apredizado de máquina desenvolvida pela Yandex, projetada especialmente para lidar eficientemente com variáveis categóricas, eliminando
    a necessidade de pré-processamento extensivo, como one-hot encoding.

  • O nome "CatBoost" deriva de "Category"e "Boosting". Ele é baseado na técnica de gradient boosting e é particularmente eficaz e eficiente, proporcionando
    resultados de alta qualidade com configurações de parâmetros padrão.

  • O CatBoost também implementa várias entratégias de regularização para prevenir overfitting, oferece a capacidade de treinar modelos de forma incremental
    e é adaptável para resolver tarefas de classificação, regressão e ranking,

  • Devido à sua robustez e facilidade de uso, o CatBoost tem ganhado popularidade na comunidade de ciência de dados para uma variedade de aplicações práticas.

image.png

1. Modelo Aditivo e Função Objetivo:

Semelhante a outros métodos de boosting, o CatBoost constrói um modelo aditivo:

$ \hat{y}(x) = \sum_{k=1}^{T} f_k(x) $

onde $f_k(x)$ são funções base (neste caso, árvores de decisão), e $T$ é o número total de árvores. O CatBoost tem como objetivo minimizar a seguinte função objetivo:

$ \text{Obj} = \text{Loss}(\mathbf{y}, \hat{\mathbf{y}}) + \text{Regularization} $

onde $\text{Loss}(\mathbf{y}, \hat{\mathbf{y}})$ é a função de perda, dependente do problema, e $\text{Regularization}$ é um termo de regularização.

2. Tratamento de Características Categóricas:

A principal característica do CatBoost é sua capacidade de lidar com variáveis categóricas. Ele utiliza um método chamado "ordered boosting", que elimina o risco de
vazamento de dados durante o cálculo de estatísticas de características categóricas.

3. Regularização:

O CatBoost utiliza a regularização para evitar o overfitting, semelhante ao XGBoost e ao LightGBM. No entanto, a formulação exata do termo de regularização é proprietária do algoritmo CatBoost.

4. Ordered Boosting:

O CatBoost introduz um esquema de boosting chamado "ordered boosting", que busca evitar o overfitting ao lidar com a escolha de splits nas variáveis categóricas.

Isso é feito utilizando diferentes esquemas de permutação para calcular as estatísticas das características categóricas durante o treinamento, o que ajuda a evitar
o vazamento de informações e melhora a robustez do modelo.

5. Função de Perda e Problemas de Classificação e Regressão:

O CatBoost pode ser utilizado para problemas de classificação binária, multiclasse e regressão. A função de perda é escolhida de acordo com o problema:

  • Para classificação binária, normalmente se usa a log loss.
  • Para multiclasse, utiliza-se uma versão multiclasse da log loss.
  • Para regressão, pode-se usar o erro quadrático médio (MSE) ou outras funções de perda contínuas.

6. Eficiência Computacional:

O CatBoost é otimizado para ser eficiente em termos de memória e tempo de execução e é paralelizável, o que permite treinar modelos em conjuntos de dados grandes de forma eficiente.

Conclusão:

O CatBoost é um algoritmo de boosting robusto e eficiente que é especialmente útil quando se lida com variáveis categóricas, devido ao seu tratamento inovador dessas variáveis.

Ele fornece alta performance e precisão em uma variedade de problemas, com a capacidade de lidar eficientemente com conjuntos de dados grandes e complexos.

from catboost import CatBoostClassifier
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definir o modelo
model = xgb.XGBClassifier(use_label_encoder=False, eval_metric='mlogloss')

# Definindo os parâmetros para o grid search
param_grid = {
    'learning_rate': [0.01, 0.1],
    'depth': [6, 8],
    'l2_leaf_reg': [1, 3],
    'iterations': [100, 200]
}

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    verbose=2,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)


# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_catbst = grid.best_estimator_
Fitting 5 folds for each of 16 candidates, totalling 80 fits
Melhores Parâmetros:  {'depth': 6, 'iterations': 100, 'l2_leaf_reg': 1, 'learning_rate': 0.1}
Melhor AUC:  0.8683085071854262
CPU times: total: 375 ms
Wall time: 2.16 s

Avaliando o modelo (CatBoost)

avaliar_modelo(X_train, y_train, X_test, y_test, best_model_catbst,nm_modelo='CatBoost')
Graph

k-Nearest Neighbors (k-NN)

O algoritmo k-Nearest Neighbors (k-NN) é um método simples e intuitivo de aprendizado de máquina usado para classificação e regressão. Ele é um tipo de aprendizado
baseado em instância ou aprendizado preguiçoso, onde a função é aproximada localmente e todo o cálculo é adiado até a avaliação da função.

1. Princípio Básico:

O k-NN funciona calculando a distância entre uma amostra de teste e todas as amostras de treinamento. As $k$ amostras de treinamento mais próximas são
selecionadas, e a previsão é feita com base nessas amostras.

2. Classificação:

Para um problema de classificação, o k-NN atribui a uma amostra de teste a classe que é mais frequente entre seus $k$ vizinhos mais próximos.

$ y_{\text{pred}} = \arg\max_{c} \sum_{i=1}^{k} w_i \cdot \mathbb{1}(y_i = c) $

Onde:

  • $y_{\text{pred}}$ é a classe prevista para a amostra de teste.
  • $w_i$ é o peso associado ao i-ésimo vizinho. Pode ser uniforme ou inversamente proporcional à distância.
  • $\mathbb{1}(y_i = c)$ é uma função indicadora, igual a 1 se a classe do i-ésimo vizinho é $c$, e 0 caso contrário.

3. Regressão:

Para um problema de regressão, o k-NN atribui à amostra de teste a média (ou mediana) dos valores de saída de seus $k$ vizinhos mais próximos.

$ y_{\text{pred}} = \frac{\sum_{i=1}^{k} w_i \cdot y_i}{\sum_{i=1}^{k} w_i} $

Onde:

  • $y_{\text{pred}}$ é o valor previsto para a amostra de teste.
  • $w_i$ é o peso associado ao i-ésimo vizinho. Pode ser uniforme ou inversamente proporcional à distância.
  • $y_i$ é o valor de saída do i-ésimo vizinho.

4. Distância:

A medida de distância mais comumente usada no k-NN é a distância Euclidiana:

$ d(x, x') = \sqrt{\sum_{j=1}^{n} (x_j - x'_j)^2} $

Onde:

  • $d(x, x')$ é a distância entre as amostras $x$ e $x'$.
  • $x_j$ e $x'_j$ são os valores da j-ésima característica das amostras $x$ e $x'$, respectivamente.

5. Exemplo com Python:

O Scikit-Learn possui uma implementação do k-NN chamada 'KNeighborsClassifier' para classificação e 'KNeighborsRegressor' para regressão.

6. Pontos a Considerar:

  • O k-NN é sensível à escala das características, então normalizar os dados é importante.
  • Ele também sofre com a "maldição da dimensionalidade", portanto, redução de dimensionalidade (por exemplo, PCA) pode ser útil em conjuntos
    de dados com muitas características.
  • O k-NN é computacionalmente intensivo durante a fase de teste, especialmente para grandes conjuntos de dados, pois calcula a distância entre uma
    amostra de teste e todas as amostras de treinamento.

Para o algoritmo k-NN, a forma como as previsões são feitas varia entre tarefas de regressão e classificação.

1. Regressão:

Na regressão, o k-NN calcula a média (ou em alguns casos, a mediana) dos valores das $ k $ observações mais próximas ao ponto de consulta.

A equação para a previsão de regressão é:

$ \hat{y}(x) = \frac{1}{k} \sum_{i=1}^{k} y_i $

Onde:

  • $ \hat{y}(x) $ é a previsão para o ponto de consulta $ x $.
  • $ k $ é o número de vizinhos mais próximos considerados.
  • $ y_i $ são os valores das $ k $ observações mais próximas.

2. Classificação:

Na classificação, o k-NN atribui a classe mais frequente entre as $ k $ observações mais próximas ao ponto de consulta.

A equação para a previsão de classificação não é uma equação numérica tradicional, mas pode ser representada como:

$ \hat{y}(x) = \text{moda} (y_1, y_2, \ldots, y_k) $

Onde:

  • $ \hat{y}(x) $ é a classe prevista para o ponto de consulta $ x $.
  • $ k $ é o número de vizinhos mais próximos considerados.
  • $ y_1, y_2, \ldots, y_k $ são as classes das $ k $ observações mais próximas.
  • "moda" refere-se à classe que aparece com mais frequência entre as $ k $ observações mais próximas.

Distância:

Ambos, regressão e classificação com k-NN, utilizam métricas de distância (como a distância Euclidiana) para encontrar os $ k $ vizinhos mais próximos:

$ \text{Distância Euclidiana: } d(x, x_i) = \sqrt{\sum_{j=1}^{n}(x_j - x_{i,j})^2} $

Onde:

  • $ x $ é o ponto de consulta.
  • $ x_i $ é um ponto no conjunto de dados.
  • $ x_j $ e $ x_{i,j} $ são as j-ésimas características de $ x $ e $ x_i $ respectivamente.
  • $ n $ é o número total de características.
  1. Distância de Manhattan (Distância $L_1$):

A distância de Manhattan entre dois pontos $x$ e $y$ em um espaço $n$-dimensional é calculada como a soma das diferenças absolutas de suas coordenadas. A equação para a distância de Manhattan é:

$ d(x, y) = \sum_{i=1}^{n} |x_i - y_i| $

Onde:

  • $x = (x_1, x_2, \ldots, x_n)$ e $y = (y_1, y_2, \ldots, y_n)$ são dois pontos no espaço $n$-dimensional.
  1. Distância de Minkowski (Distância $L_p$):

A distância de Minkowski é uma métrica de distância em um espaço normado. É uma generalização das distâncias Euclidiana e Manhattan. A equação para a distância de Minkowski é:

$ d(x, y) = \left( \sum_{i=1}^{n} |x_i - y_i|^p \right)^{1/p} $

Onde:

  • $x = (x_1, x_2, \ldots, x_n)$ e $y = (y_1, y_2, \ldots, y_n)$ são dois pontos no espaço $n$-dimensional.
  • $p$ é o parâmetro de ordem da norma.

Observações:

  • Quando $p = 1$, a distância de Minkowski se torna a distância de Manhattan.
  • Quando $p = 2$, a distância de Minkowski se torna a distância Euclidiana.

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definir o modelo
model = KNeighborsClassifier()

# Definindo os parâmetros para o grid search
param_grid = {
    'n_neighbors': [1, 3, 5, 7, 9, 11, 13, 15],
    'weights': ['uniform', 'distance'],
    'metric': ['euclidean', 'manhattan', 'minkowski']
}

# Calculando a quantidade total de modelos que serão treinados
# num_models = len(param_grid['penalty']) * len(param_grid['tol']) * len(param_grid['C']) * len(param_grid['solver']) * 5  # 5 é o número de folds na validação cruzada (cv)
# print(f"Total de Modelos a serem Treinados: {num_models}")

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    verbose=2,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)

# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_knn = grid.best_estimator_
Fitting 5 folds for each of 48 candidates, totalling 240 fits
Melhores Parâmetros:  {'metric': 'manhattan', 'n_neighbors': 15, 'weights': 'distance'}
Melhor AUC:  0.7722374098537753
CPU times: total: 281 ms
Wall time: 877 ms

Avaliando o modelo (K-NN)

avaliar_modelo(X_train, y_train, X_test, y_test, best_model_knn,nm_modelo='KNN')
Graph

Redes Neurais - MLP

As Redes Neurais Multilayer Perceptron (MLP) são uma classe de redes neurais artificiais estruturadas em múltiplas camadas, incluindo uma camada de entrada, uma ou
mais camadas ocultas e uma camada de saída.

Cada neurônio, ou unidade de processamento, em uma camada está conectado a todos os neurônios da camada seguinte por meio de pesos sinápticos.

O MLP utiliza uma função de ativação não-linear, como a função sigmoide ou ReLU, permitindo que o modelo capture relações complexas e não-lineares entre as entradas e saídas.

O treinamento do MLP é comumente realizado através do algoritmo de retropropagação (backpropagation), que ajusta os pesos sinápticos para minimizar a diferença entre
as previsões do modelo e os valores verdadeiros, utilizando técnicas de otimização como o Gradiente Descendente.

As redes MLP são aplicadas em uma ampla gama de tarefas de aprendizado de máquina, como classificação, regressão e reconhecimento de padrões, sendo fundamentais para
o desenvolvimento de modelos mais sofisticados de aprendizado profundo.

As Redes Neurais Multilayer Perceptron (MLP) envolvem várias equações para representar a propagação para a frente (feedforward), a função de ativação, a função de custo e a retropropagação (backpropagation).

  1. Propagação para Frente (Feedforward):
  • Para um neurônio em uma camada oculta ou de saída, a entrada líquida $ z $ é calculada como:

$ z = w_1x_1 + w_2x_2 + \cdots + w_nx_n + b = \mathbf{w} \cdot \mathbf{x} + b $

Onde:

  • $ \mathbf{w} $ é o vetor de pesos.
  • $ \mathbf{x} $ é o vetor de entradas.
  • $ b $ é o viés (bias).
  1. Função de Ativação:
  • A função de ativação é aplicada à entrada líquida para produzir a saída do neurônio. Para a função de ativação sigmoidal, a saída $ a $ é:

$ a = \sigma(z) = \frac{1}{1 + e^{-z}} $

  1. Função de Custo:
  • A função de custo $ J $ quantifica o erro entre a previsão da rede e o valor verdadeiro. Para problemas de classificação binária, uma função de custo comum é a entropia cruzada (cross-entropy):

$ J(\mathbf{w}, b) = - \left( y \log(a) + (1 - y) \log(1 - a) \right) $

Onde:

  • $ y $ é o valor verdadeiro.
  • $ a $ é a saída da rede.
  1. Retropropagação (Backpropagation):
  • A retropropagação é usada para atualizar os pesos e vieses da rede durante o treinamento. O gradiente da função de custo em relação aos pesos e vieses é calculado e usado para fazer atualizações.
  • A regra de atualização do gradiente descendente para um peso $ w_i $ é:

$ w_i \leftarrow w_i - \alpha \frac{\partial J}{\partial w_i} $

Onde:

  • $ \alpha $ é a taxa de aprendizado.

Essas são equações simplificadas para ilustrar os componentes principais de uma MLP. Em prática, uma MLP terá muitos neurônios em cada camada, e cada neurônio terá seu próprio
conjunto de pesos e vieses, que são ajustados durante o treinamento.

As funções de ativação são fundamentais em redes neurais, pois introduzem não-linearidades ao modelo, permitindo que a rede aprenda relações complexas entre as entradas e saídas.

Aqui estão as equações para cinco funções de ativação comumente usadas:

  1. Função Sigmoide:

$ \sigma(z) = \frac{1}{1 + e^{-z}} $

  • Range: (0, 1)
  • Usos: Camadas de saída em problemas de classificação binária.
  1. Função Tangente Hiperbólica (tanh):

$ \text{tanh}(z) = \frac{e^{2z} - 1}{e^{2z} + 1} $

  • Range: (-1, 1)
  • Usos: Camadas ocultas, quando é preferível ter média 0.
  1. Função de Ativação Linear Retificada (ReLU):

$ \text{ReLU}(z) = \max(0, z) $

  • Range: [0, $+\infty$)
  • Usos: Camadas ocultas em redes profundas, devido à sua eficiência computacional e propriedades de convergência.
  1. Função Softmax:

$ \text{Softmax}(z)_i = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}} $

  • Range: (0, 1) para cada componente, e a soma de todas as componentes é 1.
  • Usos: Camadas de saída em problemas de classificação multiclasse.
  1. Função Leaky ReLU:

$ \text{Leaky ReLU}(z) = \max(\alpha z, z) $

  • Range: (-∞, $+\infty$)
  • Usos: Camadas ocultas em redes profundas, quando é desejável permitir gradientes para $ z < 0 $.
  • Nota: $ \alpha $ é um pequeno valor positivo, como 0.01.

Cada uma dessas funções de ativação tem suas próprias características e é adequada para diferentes tipos de camadas e arquiteturas de rede.

Na Rede Neural Multilayer Perceptron (MLP), a equação de saída varia dependendo se o problema é de regressão ou classificação.

1. Para Regressão:

  • Para um problema de regressão, a saída da rede, $ \hat{y} $, é geralmente o valor bruto (sem transformação) produzido pela última camada da rede. Supondo uma única saída,
    a equação de saída para regressão pode ser dada por:

$ \hat{y} = w_1a_1 + w_2a_2 + \cdots + w_na_n + b $

Onde:

  • $ w_1, w_2, \ldots, w_n $ são os pesos conectando a última camada oculta à camada de saída.
  • $ a_1, a_2, \ldots, a_n $ são as ativações da última camada oculta.
  • $ b $ é o viés da camada de saída.

2. Para Classificação Binária:

  • Para classificação binária, a saída da rede, $ p $, é geralmente a probabilidade da classe positiva, e é obtida aplicando a função sigmoide ao valor bruto produzido pela última camada da rede.
  • A equação de saída para classificação binária seria:

$ p = \sigma(w_1a_1 + w_2a_2 + \cdots + w_na_n + b) $

Onde:

  • $ \sigma(z) = \frac{1}{1 + e^{-z}} $ é a função sigmoide.

3. Para Classificação Multiclasse:

  • Para problemas de classificação com mais de duas classes, a função softmax é frequentemente usada na camada de saída para converter os valores brutos de saída em probabilidades.
  • Se houver $ K $ classes, a equação de saída para a classe $ k $ seria:

$ p_k = \frac{e^{z_k}}{\sum_{j=1}^{K} e^{z_j}} $

Onde:

  • $ z_k = w_{k,1}a_1 + w_{k,2}a_2 + \cdots + w_{k,n}a_n + b_k $ é o valor bruto de saída para a classe $ k $.

Nota:

  • Nas equações acima, $ a_1, a_2, \ldots, a_n $ são as saídas (ativações) dos neurônios na última camada oculta, que são calculadas com base nas entradas e pesos da rede, e passadas
    por funções de ativação apropriadas, como ReLU, tanh, etc.

from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definir o modelo
model = MLPClassifier(max_iter=1000, random_state=42)

# Definindo os parâmetros para o grid search
param_grid = {
    'hidden_layer_sizes': [(50,), (50, 50)],
    'activation': ['tanh', 'relu'],
    'solver': ['sgd', 'adam'],
    'alpha': [0.0001, 0.05],
    'learning_rate': ['constant', 'adaptive'],
}

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    verbose=2,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)


# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_mlp = grid.best_estimator_
Fitting 5 folds for each of 32 candidates, totalling 160 fits
Melhores Parâmetros:  {'activation': 'tanh', 'alpha': 0.05, 'hidden_layer_sizes': (50, 50), 'learning_rate': 'constant', 'solver': 'adam'}
Melhor AUC:  0.8487093765885086
CPU times: total: 2.97 s
Wall time: 17.1 s

Avaliando o modelo (MLP)

avaliar_modelo(X_train, y_train, X_test, y_test, best_model_mlp,nm_modelo='MLP')
Graph

SVM

O Support Vector Machine (SVM) é um modelo poderoso e versátil de aprendizado de máquina, amplamente utilizado para tarefas de classificação e regressão.

O princípio fundamental do SVM é encontrar o hiperplano que separa as diferentes classes no espaço de características de maneira ótima, sendo este definido como o hiperplano
que maximiza a margem entre as classes mais próximas, chamadas de vetores de suporte.

Em casos não linearmente separáveis, o SVM utiliza funções de kernel, como o kernel polinomial ou o kernel RBF (Radial Basis Function), para mapear os dados para um espaço
de dimensão superior onde eles possam ser linearmente separados, uma técnica conhecida como truque do kernel.

O SVM é valorizado por sua robustez e eficácia em espaços de alta dimensão e situações onde a margem de separação entre as classes é pequena, sendo uma ferramenta essencial
para cientistas de dados em diversas aplicações práticas.

O Support Vector Machine (SVM) tem como objetivo encontrar um hiperplano que separe as classes ou que faça a regressão de forma ótima. Vamos detalhar as principais equações, começando com classificação e depois abordando regressão:

1. SVM para Classificação:

  • Equação do Hiperplano:

$ w \cdot x + b = 0 $

Onde $ w $ é o vetor normal ao hiperplano, $ x $ são os pontos no espaço de características, e $ b $ é o viés.

  • Decisão de Classificação:

$ f(x) = \text{sign}(w \cdot x + b) $

Onde a função $\text{sign}(\cdot)$ retorna +1 se o argumento é positivo (pertence à classe +1) e -1 se o argumento é negativo (pertence à classe -1).

  • Objetivo de Otimização (Problema Primal):

$ \min_{w, b} \frac{1}{2} \|w\|^2 $

$ \text{sujeito a } y_i(w \cdot x_i + b) \geq 1, \forall i $

  • Funções de Kernel:

Quando se lida com dados não linearmente separáveis, usa-se o truque do kernel para mapear os dados para um espaço de maior dimensão. A função de kernel mais usada é a RBF:

$ K(x_i, x_j) = \exp(-\gamma \|x_i - x_j\|^2) $

Onde $ \gamma > 0 $ é o parâmetro do kernel RBF.

2. SVM para Regressão (SVR - Support Vector Regression):

  • Modelo de Regressão:

$ f(x) = w \cdot x + b $

Onde o objetivo é fazer com que $ f(x) $ esteja o mais próximo possível dos valores de saída $ y_i $ para todos os pontos de dados, com uma tolerância de erro $ \epsilon $.

  • Objetivo de Otimização:

$ \min_{w, b} \frac{1}{2} \|w\|^2 + C \sum_{i=1}^{n} (\xi_i + \xi_i^*) $

$ \text{sujeito a } \begin{cases} y_i - w \cdot x_i - b \leq \epsilon + \xi_i \\ w \cdot x_i + b - y_i \leq \epsilon + \xi_i^* \\ \xi_i, \xi_i^* \geq 0 \end{cases} $

Onde:

  • $ \xi_i $ e $ \xi_i^* $ são as variáveis de folga que medem o desvio fora da banda de erro $ \epsilon $.
  • $ C > 0 $ é o parâmetro de regularização que controla o equilíbrio entre a complexidade do modelo e a quantidade permitida de violações da banda de erro.

Resumo:

  • Para classificação, a saída é uma decisão de classe (+1 ou -1) baseada no sinal da função de decisão.
  • Para regressão, a saída é um valor contínuo representando a previsão do modelo.

Para converter as saídas de um modelo de classificação, como SVM, em probabilidades, uma abordagem comum é usar a função logística (ou sigmoide) aplicada à distância
da instância até o hiperplano de decisão.

A ideia é mapear a distância de um ponto até o hiperplano de uma maneira que valores grandes (ponto longe do hiperplano no lado positivo) se aproximem de 1, e valores
pequenos (ponto longe do hiperplano no lado negativo) se aproximem de 0.

A função sigmoide, que é usada para este propósito, tem a forma:

$ P(y=1 | x) = \frac{1}{1 + \exp(-f(x))} $

Onde:

  • $ P(y=1 | x) $ é a probabilidade da classe ser 1 dado o ponto $ x $.
  • $ f(x) = w \cdot x + b $ é a função de decisão do SVM.

SVM com Probabilidade no scikit-learn:

No scikit-learn, o SVM tem um parâmetro probability que, quando definido como True, faz com que o modelo ajuste uma regressão logística sobre as pontuações da função de decisão para obter probabilidades.

Isso é feito usando validação cruzada e, consequentemente, pode aumentar significativamente o tempo de treinamento do modelo.

Exemplo de Código:

python

from sklearn import svm

Criar um SVM com um kernel RBF

classifier = svm.SVC(kernel='rbf', probability=True)

Treinar o modelo com os dados de treinamento (X_train, y_train)

classifier.fit(X_train, y_train)

Obter as probabilidades para os dados de teste (ou novos dados)

probabilities = classifier.predict_proba(X_test)

Neste exemplo, 'probabilities' será um array onde cada linha corresponde a uma instância de teste e cada coluna contém a probabilidade da instância pertencer a uma classe.

Em otimização matemática, o problema primal refere-se ao problema de otimização original que se deseja resolver.

Muitas vezes, ao resolver problemas de otimização, particularmente em aprendizado de máquina e pesquisa operacional, é comum formular um problema dual associado ao problema primal.

O problema dual é derivado do problema primal e, em muitos casos, pode ser mais fácil de resolver, oferecendo ainda insights sobre o problema primal.

Formulação Primal:

Dado um problema de otimização genérico, o problema primal pode ser formulado da seguinte maneira:

$ \begin{align*} &\min_{\mathbf{x}} \quad f(\mathbf{x})\\ &\text{s.a.} \quad g_i(\mathbf{x}) \leq 0, \quad i = 1, ..., m\\ &\quad h_j(\mathbf{x}) = 0, \quad j = 1, ..., p \end{align*} $

Onde:

  • $ \mathbf{x} $ é o vetor de variáveis de decisão.
  • $ f(\mathbf{x}) $ é a função objetivo que se deseja minimizar.
  • $ g_i(\mathbf{x}) $ são as restrições de desigualdade.
  • $ h_j(\mathbf{x}) $ são as restrições de igualdade.

Exemplo: Problema Primal no SVM:

No contexto de Support Vector Machines (SVM), o problema primal é encontrar o hiperplano de separação que maximiza a margem entre duas classes, enquanto minimiza a soma
das distâncias dos pontos mal classificados ao hiperplano. O problema primal do SVM pode ser formulado como:

$ \begin{align*} &\min_{\mathbf{w}, b, \mathbf{\xi}} \quad \frac{1}{2} \| \mathbf{w} \|^2 + C \sum_{i=1}^{n} \xi_i\\ &\text{s.a.} \quad y_i(\mathbf{w} \cdot \mathbf{x_i} + b) \geq 1 - \xi_i, \quad \xi_i \geq 0, \quad i = 1, ..., n \end{align*} $

Onde:

  • $ \mathbf{w} $ e $ b $ definem o hiperplano.
  • $ \xi_i $ são as variáveis de folga que medem o grau de violação da margem.
  • $ C > 0 $ é o parâmetro de regularização.

Problema Dual:

O problema dual, derivado do primal, frequentemente envolve a maximização da função lagrangiana dual, e suas variáveis são os multiplicadores de Lagrange
associados às restrições do problema primal.

Resolver o problema dual pode oferecer insights e propriedades úteis relacionadas ao problema primal, e em muitos casos, a solução do problema dual fornece
uma maneira de encontrar a solução do problema primal.

from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
import numpy as np

%%time
# Definir o modelo
model = SVC(probability=True)

# Definindo os parâmetros para o grid search
param_grid = {
    'C': [0.1, 1],
    'kernel': ['linear'],
    # 'degree': [2, 3],  # Só é relevante se kernel='poly'
    'gamma': [0.1, 1]
}

# Definindo o objeto GridSearchCV
grid = GridSearchCV(model,
                    param_grid,
                    cv=5,
                    verbose=2,
                    scoring='roc_auc',  # 'accuracy' 'precision' 'recall' 'f1'
                    n_jobs=-1)

# Treinando o modelo com o grid search
grid.fit(X_train, y_train)


# Exibindo os melhores parâmetros encontrados pelo grid search
print("Melhores Parâmetros: ", grid.best_params_)

# Exibindo a melhor pontuação (score) atingida pelo modelo com os melhores parâmetros
print("Melhor AUC: ", grid.best_score_)

# Utilizando o melhor modelo para fazer previsões
predictions = grid.best_estimator_.predict(X_test)

best_model_svm = grid.best_estimator_
Fitting 5 folds for each of 4 candidates, totalling 20 fits
Melhores Parâmetros:  {'C': 0.1, 'gamma': 0.1, 'kernel': 'linear'}
Melhor AUC:  0.835162980050508
CPU times: total: 57.1 s
Wall time: 5min 39s

Avaliando o modelo (SVM)

avaliar_modelo(X_train, y_train, X_test, y_test, best_model_svm,nm_modelo='SVM')
Graph

Stack de Modelos

O stacking de modelos é uma técnica de aprendizado de conjunto que busca combinar múltiplos modelos de aprendizado de máquina para melhorar o desempenho preditivo geral.

Nessa abordagem, diferentes modelos, chamados de modelos de base, são treinados usando o conjunto de dados de treinamento e, em seguida, um modelo meta-aprendiz
(ou meta-modelo) é treinado para fazer previsões finais baseadas nas previsões dos modelos de base.

Os modelos de base podem ser diferentes tipos de modelos ou o mesmo tipo de modelo com diferentes configurações de hiperparâmetros.

O meta-modelo aprende como otimizar a combinação das previsões dos modelos de base de forma a produzir uma previsão final mais precisa e robusta.

O stacking é particularmente útil quando os modelos de base têm diferentes forças e fraquezas em relação a diferentes partes do espaço de entrada, permitindo que
o modelo de conjunto tire proveito da diversidade dos modelos de base para alcançar um desempenho superior.

No contexto do stacking de modelos, tanto para classificação quanto para regressão, as equações envolvidas são principalmente relacionadas à forma como o meta-modelo
faz previsões baseadas nas previsões dos modelos de base.

1. Para Regressão:

Modelos de Base:

Suponha que temos $ M $ modelos de base e queremos fazer previsões para uma instância $ x $. Cada modelo de base $ m_i $ produzirá uma previsão $ \hat{y}_i $.

$ \hat{y}_1 = m_1(x) $

$ \hat{y}_2 = m_2(x) $

$ \vdots $

$ \hat{y}_M = m_M(x) $

Meta-Modelo:

O meta-modelo é treinado para fazer previsões $ \hat{y} $ usando as previsões dos modelos de base como entradas.

$ \hat{y} = f(\hat{y}_1, \hat{y}_2, \ldots, \hat{y}_M) $

2. Para Classificação:

Modelos de Base:

Para a classificação, cada modelo de base $ m_i $ produzirá uma probabilidade $ p_i $ da instância $ x $ pertencer à classe positiva.

$ p_1 = m_1(x) $

$ p_2 = m_2(x) $

$ \vdots $

$ p_M = m_M(x) $

Meta-Modelo:

O meta-modelo é então treinado para fazer previsões de classe (ou probabilidades de classe) usando as probabilidades dos modelos de base como entradas.

$ \hat{p} = f(p_1, p_2, \ldots, p_M) $

Onde $ \hat{p} $ é a probabilidade prevista da instância $ x $ pertencer à classe positiva, e a classe final prevista será determinada com base em um limiar, como 0.5 em problemas binários.

Nota:

A função $ f $ do meta-modelo pode ser qualquer modelo de aprendizado de máquina adequado, como uma regressão logística para problemas de classificação, ou uma regressão linear para
problemas de regressão, dependendo do problema específico em questão.

O treinamento do meta-modelo envolve o uso de um conjunto de validação ou de um procedimento de validação cruzada para evitar o ajuste excessivo ao conjunto de treinamento.

from sklearn.ensemble import StackingClassifier
from sklearn.linear_model import LogisticRegression

%%time
# Definir os modelos de base
base_learners = [
    ('dt', best_model_dt),
    ('rl', best_model_rl),
    ('rf', best_model_rf),
    ('gbm', best_model_gbm),
    ('lgbm', best_model_lightgbm),
    ('xgbm', best_model_xgbm),
    ('ctbst', best_model_catbst),
    ('knn', best_model_knn),
    ('mlp', best_model_mlp),
    ('svc', best_model_svm)
]

# Definir o meta-modelo
meta_learner = LogisticRegression(random_state=42)

# Construir o modelo de stacking
stacking_model = StackingClassifier(estimators=base_learners, final_estimator=meta_learner)

# Treinar o modelo de stacking
stacking_model.fit(X_train, y_train)

stacking_model
CPU times: total: 5min 11s
Wall time: 5min 3s
StackingClassifier(estimators=[('dt',
                                DecisionTreeClassifier(criterion='entropy',
                                                       max_depth=4,
                                                       min_samples_leaf=0.008,
                                                       min_samples_split=0.008,
                                                       random_state=42,
                                                       splitter='random')),
                               ('rl',
                                LogisticRegression(C=2.0,
                                                   class_weight={0: 1, 1: 2},
                                                   penalty='l1',
                                                   solver='liblinear',
                                                   tol=0.001)),
                               ('rf',
                                RandomForestClassifier(max_depth=5,
                                                       min_samples_leaf=0.01,
                                                       min_samples_sp...
                                              multi_strategy=None,
                                              n_estimators=None, ...)),
                               ('knn',
                                KNeighborsClassifier(metric='manhattan',
                                                     n_neighbors=15,
                                                     weights='distance')),
                               ('mlp',
                                MLPClassifier(activation='tanh', alpha=0.05,
                                              hidden_layer_sizes=(50, 50),
                                              max_iter=1000, random_state=42)),
                               ('svc',
                                SVC(C=0.1, gamma=0.1, kernel='linear',
                                    probability=True))],
                   final_estimator=LogisticRegression(random_state=42))
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Avaliando o modelo (Stacking)

avaliar_modelo(X_train, y_train, X_test, y_test, stacking_model,nm_modelo='Stacking')
Graph

Blend de Modelos

O blending de modelos é uma técnica de ensemble similar ao stacking, onde diferentes modelos de aprendizado de máquina são combinados para criar um
modelo composto mais robusto e preciso.

A diferença principal entre o blending e o stacking é a forma como as previsões dos modelos individuais são combinadas.

No blending, ao invés de usar um meta-modelo para aprender a melhor forma de combinar as previsões, as previsões dos modelos individuais são combinadas
de maneira mais simples, geralmente através de médias ponderadas, votações majoritárias ou outras técnicas de agregação.

Por exemplo, em um problema de regressão, o resultado final do blending pode ser a média ponderada das previsões dos modelos individuais, onde os pesos são
determinados com base no desempenho de validação de cada modelo.

Já em problemas de classificação, pode-se utilizar a moda ou a média das probabilidades preditas.

O blending é uma técnica menos flexível que o stacking, pois não permite que o modelo aprenda interações complexas entre as previsões dos modelos individuais,
mas pode ser mais simples e menos propenso a overfitting, dependendo do contexto.

No contexto do blending de modelos, as equações para combinar modelos em tarefas de regressão e classificação são geralmente bastante simples, dependendo principalmente
de como você escolhe combinar as previsões dos modelos individuais.

1. Para Regressão:

Modelos Individuais: Suponha que temos $ M $ modelos individuais, e cada modelo $ m_i $ faz uma previsão $ \hat{y}_i $ para uma instância $ x $.

$ \hat{y}_1 = m_1(x) $

$ \hat{y}_2 = m_2(x) $

$ \vdots $

$ \hat{y}_M = m_M(x) $

Blending:

No blending, você pode combinar as previsões dos modelos individuais usando, por exemplo, uma média (possivelmente ponderada):

$ \hat{y} = \frac{1}{M} \sum_{i=1}^{M} \hat{y}_i $

$ \text{ou} $

$ \hat{y} = \sum_{i=1}^{M} w_i \hat{y}_i $

Onde $ w_i $ são pesos que somam 1 e podem ser atribuídos com base no desempenho de validação de cada modelo.

2. Para Classificação:

Modelos Individuais:

Cada modelo $ m_i $ produzirá uma probabilidade $ p_i $ da instância $ x $ pertencer à classe positiva.

$ p_1 = m_1(x) $

$ p_2 = m_2(x) $

$ \vdots $

$ p_M = m_M(x) $

Blending:

Para classificação, o blending pode envolver o cálculo da média das probabilidades preditas (ou votação majoritária em caso de classificações diretas):

$ \hat{p} = \frac{1}{M} \sum_{i=1}^{M} p_i $

$ \text{ou} $

$ \hat{p} = \sum_{i=1}^{M} w_i p_i $

E a classe final prevista pode ser determinada com base em um limiar, como 0.5 em problemas binários:

$ \hat{y} = \begin{cases} 1 & \text{se } \hat{p} \geq 0.5 \\ 0 & \text{se } \hat{p} < 0.5 \end{cases} $

Nota:

Os pesos $ w_i $ podem ser definidos com base no desempenho dos modelos em um conjunto de validação, e a escolha do método de blending (média, média ponderada,
votação majoritária, etc.) dependerá do problema específico e do contexto em que os modelos estão sendo aplicados.

from sklearn.metrics import accuracy_score
import numpy as np

%%time
# Obter as probabilidades preditas pelos modelos treinados: MLP e Random Forest
probas_model1 = best_model_rf.predict_proba(X_test)
probas_model2 = best_model_mlp.predict_proba(X_test)

# Definir os pesos para os modelos
weights = [0.6, 0.4]

# Calcular a média ponderada das probabilidades
blended_probas = weights[0] * probas_model1 + weights[1] * probas_model2

# Determinar as classes preditas com base nas probabilidades combinadas
blended_predictions = np.argmax(blended_probas, axis=1)

# Avaliar o modelo combinado no conjunto de teste
score = accuracy_score(y_test, blended_predictions)

print(f'Blended Model Accuracy: {score:.2f}')
Blended Model Accuracy: 0.79
CPU times: total: 78.1 ms
Wall time: 36 ms

from sklearn.metrics import roc_curve, auc, confusion_matrix, roc_auc_score
from sklearn.preprocessing import label_binarize
import matplotlib.pyplot as plt
from scipy import interp<>from itertools import cycle

# Função para calcular Gini
def gini_index(auc_score):
    return 2 * auc_score - 1

# Calcular as probabilidades, previsões e métricas para treino e teste
for dataset_name, X, y in zip(['Train', 'Test'], [X_train, X_test], [y_train, y_test]):
    # Obter as probabilidades preditas pelos modelos
    probas_model1 = best_model_rf.predict_proba(X)
    probas_model2 = best_model_mlp.predict_proba(X)

    # Calcular a média ponderada das probabilidades
    blended_probas = weights[0] * probas_model1 + weights[1] * probas_model2

    # Determinar as classes preditas com base nas probabilidades combinadas
    blended_predictions = np.argmax(blended_probas, axis=1)

    # Calcular o AUC-ROC e Gini
    y_bin = label_binarize(y, classes=np.unique(y))
    if y_bin.shape[1] == 1:
        y_bin = np.hstack((1 - y_bin, y_bin))

    fpr = dict()
    tpr = dict()
    roc_auc = dict()
    for i in range(y_bin.shape[1]):
        fpr[i], tpr[i], _ = roc_curve(y_bin[:, i], blended_probas[:, i])
        roc_auc[i] = auc(fpr[i], tpr[i])

    # Calcular o micro-average ROC curve e ROC area
    fpr["micro"], tpr["micro"], _ = roc_curve(y_bin.ravel(), blended_probas.ravel())
    roc_auc["micro"] = auc(fpr["micro"], tpr["micro"])

    # Calcular a matriz de confusão
    cm = confusion_matrix(y, blended_predictions)

    # Exibir as métricas
    print(f'{dataset_name} Set:')
    print('Confusion Matrix:')
    print(cm)
    print('ROC-AUC Scores per Class:', roc_auc)
    print('Gini Indexes per Class:', {k: gini_index(v) for k, v in roc_auc.items()})
    print()

    # Plotar a curva ROC para cada classe
    plt.figure(figsize=(10, 8))
    lw = 2
    colors = cycle(['aqua', 'darkorange', 'cornflowerblue'])
    for i, color in zip(range(y_bin.shape[1]), colors):
        plt.plot(fpr[i], tpr[i], color=color, lw=lw,
                 label=f'ROC curve of class {i} (area = {roc_auc[i]:0.2f})')

    plt.plot([0, 1], [0, 1], 'k--', lw=lw)
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.title(f'Receiver Operating Characteristic - {dataset_name} Set')
    plt.legend(loc="lower right")
    plt.show()
Train Set:
Confusion Matrix:
[[367  25]
 [ 73 158]]
ROC-AUC Scores per Class: {0: 0.9019182348263981, 1: 0.9019182348263981, 'micro': 0.9139796304836794}
Gini Indexes per Class: {0: 0.8038364696527962, 1: 0.8038364696527962, 'micro': 0.8279592609673587}

Graph
Test Set:
Confusion Matrix:
[[143  14]
 [ 41  70]]
ROC-AUC Scores per Class: {0: 0.8666437137774717, 1: 0.8666437137774716, 'micro': 0.8745126977055022}
Gini Indexes per Class: {0: 0.7332874275549435, 1: 0.7332874275549432, 'micro': 0.7490253954110044}

Graph

Salvando artefatos dos Modelos

# Supondo que best_model_gbm seja o seu modelo treinado
with open('best_model_dt.pkl', 'wb') as file:
  pickle.dump(best_model_dt, file)

with open('best_model_rl.pkl', 'wb') as file:
  pickle.dump(best_model_gbm, file)

with open('best_model_rf.pkl', 'wb') as file:
  pickle.dump(best_model_gbm, file)

with open('best_model_gbm.pkl', 'wb') as file:
  pickle.dump(best_model_gbm, file)

with open('best_model_lightgbm.pkl', 'wb') as file:
  pickle.dump(best_model_lightgbm, file)

with open('best_model_xgbm.pkl', 'wb') as file:
  pickle.dump(best_model_lightgbm, file)

with open('best_model_catbst.pkl', 'wb') as file:
  pickle.dump(best_model_catbst, file)

with open('best_model_knn.pkl', 'wb') as file:
  pickle.dump(best_model_knn, file)

with open('best_model_mlp.pkl', 'wb') as file:
  pickle.dump(best_model_knn, file)

with open('best_model_svm.pkl', 'wb') as file:
  pickle.dump(best_model_svm, file)

Lendo os modelos treinados

# Lendo os modelos treinados
with open('best_model_rl.pkl', 'rb') as file:
    loaded_model_rl = pickle.load(file)

with open('best_model_rf.pkl', 'rb') as file:
    loaded_model_rf = pickle.load(file)

with open('best_model_gbm.pkl', 'rb') as file:
    loaded_model_gbm = pickle.load(file)

with open('best_model_lightgbm.pkl', 'rb') as file:
    loaded_model_lightgbm = pickle.load(file)

with open('best_model_xgbm.pkl', 'rb') as file:
    loaded_model_xgbm = pickle.load(file)

with open('best_model_catbst.pkl', 'rb') as file:
    loaded_model_catbst = pickle.load(file)

with open('best_model_knn.pkl', 'rb') as file:
    loaded_model_knn = pickle.load(file)

with open('best_model_mlp.pkl', 'rb') as file:
    loaded_model_mlp = pickle.load(file)

with open('best_model_svm.pkl', 'rb') as file:
    loaded_model_svm = pickle.load(file)

loaded_model_gbm
GradientBoostingClassifier(learning_rate=0.01, max_depth=7, max_features='sqrt',
                           min_samples_split=4, random_state=42, subsample=0.8)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

import pandas as pd
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score

def evaluate_models(X_train, y_train, X_test, y_test, models):
    metrics = []
    for name, model in models.items():
        # Prever os rótulos para os conjuntos de treino e teste
        train_preds = model.predict(X_train)
        test_preds = model.predict(X_test)

        # Calcular as métricas
        accuracy = accuracy_score(y_test, test_preds)
        precision = precision_score(y_test, test_preds)
        recall = recall_score(y_test, test_preds)
        f1 = f1_score(y_test, test_preds)
        auc = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])  # Supondo que é um problema de classificação binária
        Gini = 2*auc - 1

        # Adicionar ao array de métricas
        metrics.append({
            'Model': name,
            # 'Train Accuracy': accuracy_score(y_train, train_preds),
            'Accuracy': accuracy,
            'Precision': precision,
            'Recall': recall,
            'F1 Score': f1,
            'AUC-ROC': auc,
            'Gini':Gini
        })

    # Converter o array de métricas em um DataFrame
    metrics_df = pd.DataFrame(metrics)

    # Ordenar o DataFrame pela metrica Gini
    metrics_df_sorted = metrics_df.sort_values(by='Gini',ascending=False)

    # Função para destacar o maior valor em azul claro
    def highlight_max(s):
      is_max = s == s.max()
      return ['background-color: lightblue' if v else '' for v in is_max]

    # Aplicando o estilo na coluna 'A'
    metrics_df_sorted = metrics_df_sorted.style.apply(highlight_max, subset=['Gini'])


    return metrics_df_sorted

# Supondo que loaded_model_gbm seja o modelo carregado
models = {'Regressão Logística': loaded_model_rl,
          'Random Forest': loaded_model_rf,
          'Gradient Boosting': loaded_model_gbm,
          'LightGBM': loaded_model_lightgbm,
          'XGBoost': loaded_model_xgbm,
          'CatBoost': loaded_model_catbst,
          'KNN': loaded_model_knn,
          'Redes Neurais - MLP': loaded_model_mlp,
          'SVM': loaded_model_svm,
          }

# Chamar a função com os datasets de treino e teste e os modelos carregados
metrics_df = evaluate_models(X_train, y_train, X_test, y_test, models)
metrics_df
  Model Accuracy Precision Recall F1 Score AUC-ROC Gini
8 SVM 0.791045 0.772277 0.702703 0.735849 0.889482 0.778964
0 Regressão Logística 0.820896 0.888889 0.648649 0.750000 0.867906 0.735812
1 Random Forest 0.820896 0.888889 0.648649 0.750000 0.867906 0.735812
2 Gradient Boosting 0.820896 0.888889 0.648649 0.750000 0.867906 0.735812
3 LightGBM 0.809701 0.865854 0.639640 0.735751 0.866099 0.732197
4 XGBoost 0.809701 0.865854 0.639640 0.735751 0.866099 0.732197
5 CatBoost 0.802239 0.802083 0.693694 0.743961 0.843490 0.686980
6 KNN 0.735075 0.738095 0.558559 0.635897 0.777529 0.555058
7 Redes Neurais - MLP 0.735075 0.738095 0.558559 0.635897 0.777529 0.555058
%reload_ext watermark
%watermark -a "Roberto Soares - LfLngLrnng"
Author: Roberto Soares - LfLngLrnng

%watermark -v -m
Python implementation: CPython
Python version       : 3.11.9
IPython version      : 8.25.0

Compiler    : MSC v.1916 64 bit (AMD64)
OS          : Windows
Release     : 10
Machine     : AMD64
Processor   : Intel64 Family 6 Model 158 Stepping 9, GenuineIntel
CPU cores   : 4
Architecture: 64bit

%watermark --iversions
matplotlib: 3.8.4
numpy     : 1.26.4
pandas    : 2.2.2
xgboost   : 2.0.3
lightgbm  : 4.3.0

Fim

!jupyter nbconvert --to html --template=D:/anaconda3/envs/spark-pod-env/share/jupyter/nbconvert/templates/compatibility/mytemplate_a.tpl A013_Problemas_de_Classificacao_Algoritmos.ipynb
[NbConvertApp] Converting notebook A013_Problemas_de_Classificacao_Algoritmos.ipynb to html
[NbConvertApp] Writing 9909193 bytes to A013_Problemas_de_Classificacao_Algoritmos.html